80 votes

Comment puis-je animer le mouvement d'une vue ou d'une image le long d'une trajectoire courbe ?

Je développe une application de commerce. Lorsque j'ajoute un article au panier, je veux créer un effet où une image de l'article suit une trajectoire courbe et aboutit à l'onglet du panier.

Comment puis-je créer une animation d'une image le long d'une courbe comme celle-ci ?

0 votes

Images animées sur l'iPhone Référez-vous à ce lien. Il vous aidera sûrement. Merci.

153voto

Brad Larson Points 122629

Pour développer ce que Nikolai a dit, la meilleure façon de gérer cela est d'utiliser Core Animation pour animer le mouvement de l'image ou de la vue le long d'une trajectoire de Bézier. Ceci est réalisé en utilisant une CAKeyframeAnimation. Par exemple, j'ai utilisé le code suivant pour animer l'image d'une vue en une icône indiquant l'enregistrement (comme on peut le voir dans l'image de la page d'accueil). vidéo pour cette application ) :

Tout d'abord, importez le fichier d'en-tête de QuartzCore #import <QuartzCore/QuartzCore.h>

UIImageView *imageViewForAnimation = [[UIImageView alloc] initWithImage:imageToAnimate];
imageViewForAnimation.alpha = 1.0f;
CGRect imageFrame = imageViewForAnimation.frame;
//Your image frame.origin from where the animation need to get start
CGPoint viewOrigin = imageViewForAnimation.frame.origin;
viewOrigin.y = viewOrigin.y + imageFrame.size.height / 2.0f;
viewOrigin.x = viewOrigin.x + imageFrame.size.width / 2.0f;

imageViewForAnimation.frame = imageFrame;
imageViewForAnimation.layer.position = viewOrigin;
[self.view addSubview:imageViewForAnimation];

// Set up fade out effect
CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[fadeOutAnimation setToValue:[NSNumber numberWithFloat:0.3]];
fadeOutAnimation.fillMode = kCAFillModeForwards;
fadeOutAnimation.removedOnCompletion = NO;

// Set up scaling
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];
resizeAnimation.fillMode = kCAFillModeForwards;
resizeAnimation.removedOnCompletion = NO;

// Set up path movement
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
//Setting Endpoint of the animation
CGPoint endPoint = CGPointMake(480.0f - 30.0f, 40.0f);
//to end animation in last tab use 
//CGPoint endPoint = CGPointMake( 320-40.0f, 480.0f);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, viewOrigin.x, viewOrigin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, viewOrigin.y, endPoint.x, viewOrigin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);

CAAnimationGroup *group = [CAAnimationGroup animation]; 
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, resizeAnimation, nil]];
group.duration = 0.7f;
group.delegate = self;
[group setValue:imageViewForAnimation forKey:@"imageViewBeingAnimated"];

[imageViewForAnimation.layer addAnimation:group forKey:@"savingAnimation"];

[imageViewForAnimation release];

0 votes

J'ai compris, mais je veux créer un effet d'animation, par exemple, si j'ai une grande image, je dois redimensionner l'image au centre de la grande image, puis ajouter un chemin d'animation.

0 votes

Je ne suis pas sûr de ce que vous demandez, mais l'animation ci-dessus fait trois choses : elle déplace l'image le long d'une trajectoire courbe, la rétrécit au fur et à mesure de son déplacement pour qu'elle soit minuscule au moment où elle atteint le point final, et la fait disparaître en fondu au fur et à mesure de son déplacement. Vous pouvez ajuster le redimensionnement en modifiant les valeurs de départ et d'arrivée, selon vos besoins.

0 votes

Que dois-je inclure pour que Xcode reconnaisse tout le CAKey...stuff ? Il dit qu'il ne trouve pas les symboles.

4voto

Vlad Points 1510

La façon d'animer le long de CGPath en utilisant UIView.animateKeyframes (Swift 4)

private func animateNew() {

   let alphaFrom: CGFloat = 1
   let alphaTo: CGFloat = 0.3
   let sizeFrom = CGSize(width: 40, height: 20)
   let sizeTo = CGSize(width: 80, height: 60)
   let originFrom = CGPoint(x: 40, y: 40)
   let originTo = CGPoint(x: 240, y: 480)

   let deltaWidth = sizeTo.width - sizeFrom.width
   let deltaHeight = sizeTo.height - sizeFrom.height
   let deltaAlpha = alphaTo - alphaFrom

   // Setting default values
   imageViewNew.alpha = alphaFrom
   imageViewNew.frame = CGRect(origin: originFrom, size: sizeFrom)

   // CGPath setup for calculating points on curve.
   let curvedPath = CGMutablePath()
   curvedPath.move(to: originFrom)
   curvedPath.addQuadCurve(to: originTo, control: CGPoint(x: originFrom.x,  y: originTo.y))
   let path = Math.BezierPath(cgPath: curvedPath, approximationIterations: 10)

   // Calculating timing parameters
   let duration: TimeInterval = 0.7
   let numberOfKeyFrames = 16
   let curvePoints = Math.Easing.timing(numberOfSteps: numberOfKeyFrames, .easeOutQuad)

   UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeCubic], animations: {
      // Iterating curve points and adding key frames
      for point in curvePoints {
         let origin = path.point(atPercentOfLength: point.end)
         let size = CGSize(width: sizeFrom.width + deltaWidth * point.end,
                           height: sizeFrom.height + deltaHeight * point.end)
         let alpha = alphaFrom + deltaAlpha * point.end
         UIView.addKeyframe(withRelativeStartTime: TimeInterval(point.start), relativeDuration: TimeInterval(point.duration)) {
            self.imageViewNew.frame = CGRect(origin: origin, size: size)
            self.imageViewNew.alpha = alpha
         }
      }
   }, completion: nil)
}

Dossier : Math.Easing.swift

// Inspired by: RBBAnimation/RBBEasingFunction.m: https://github.com/robb/RBBAnimation/blob/master/RBBAnimation/RBBEasingFunction.m
extension Math { public struct Easing { } }

extension Math.Easing {

   public enum Algorithm: Int {
      case linear, easeInQuad, easeOutQuad, easeInOutQuad
   }

   @inline(__always)
   public static func linear(_ t: CGFloat) -> CGFloat {
      return t
   }

   @inline(__always)
   public static func easeInQuad(_ t: CGFloat) -> CGFloat  {
      return t * t
   }

   @inline(__always)
   public static func easeOutQuad(_ t: CGFloat) -> CGFloat  {
      return t * (2 - t)
   }

   @inline(__always)
   public static func easeInOutQuad(_ t: CGFloat) -> CGFloat  {
      if t < 0.5 {
         return 2 * t * t
      } else {
         return -1 + (4 - 2 * t) * t
      }
   }
}

extension Math.Easing {

   public struct Timing {

      public let start: CGFloat
      public let end: CGFloat
      public let duration: CGFloat

      init(start: CGFloat, end: CGFloat) {
         self.start = start
         self.end = end
         self.duration = end - start
      }

      public func multiplying(by: CGFloat) -> Timing {
         return Timing(start: start * by, end: end * by)
      }
   }

   public static func process(_ t: CGFloat, _ algorithm: Algorithm) -> CGFloat {
      switch algorithm {
      case .linear:
         return linear(t)
      case .easeInQuad:
         return easeInQuad(t)
      case .easeOutQuad:
         return easeOutQuad(t)
      case .easeInOutQuad:
         return easeInOutQuad(t)
      }
   }

   public static func timing(numberOfSteps: Int, _ algorithm: Algorithm) -> [Timing] {
      var result: [Timing] = []
      let linearStepSize = 1 / CGFloat(numberOfSteps)
      for step in (0 ..< numberOfSteps).reversed() {
         let linearValue = CGFloat(step) * linearStepSize
         let processedValue = process(linearValue, algorithm) // Always in range 0 ... 1
         let lastValue = result.last?.start ?? 1
         result.append(Timing(start: processedValue, end: lastValue))
      }
      result = result.reversed()
      return result
   }
}

Dossier : Math.BezierPath.swift . Regardez sur cette réponse SO : https://stackoverflow.com/a/50782971/1418981

enter image description here

2voto

Nikolai Ruhe Points 45433

Vous pouvez animer la propriété centrale d'une UIView à l'aide d'une CAKeyframeAnimation. Voir la page CoreAnimation guide de programmation.

0voto

Vlad Points 1510

Version de Swift 4 similaire à l'exemple ObjC de la réponse originale.

class KeyFrameAnimationsViewController: ViewController {

   let sampleImage = ImageFactory.image(size: CGSize(width: 160, height: 120), fillColor: .blue)

   private lazy var imageView = ImageView(image: sampleImage)
   private lazy var actionButton = Button(title: "Animate").autolayoutView()

   override func setupUI() {
      view.addSubviews(imageView, actionButton)
      view.backgroundColor = .gray
   }

   override func setupLayout() {
      LayoutConstraint.withFormat("|-[*]", actionButton).activate()
      LayoutConstraint.withFormat("V:|-[*]", actionButton).activate()
   }

   override func setupHandlers() {
      actionButton.setTouchUpInsideHandler { [weak self] in
         self?.animate()
      }
   }

   private func animate() {

      imageView.alpha = 1
      let isRemovedOnCompletion = false

      let sizeFrom = CGSize(width: 40, height: 20)
      let sizeTo = CGSize(width: 80, height: 60)
      let originFrom = CGPoint(x: 40, y: 40)
      let originTo = CGPoint(x: 240, y: 480)

      imageView.frame = CGRect(origin: originFrom, size: sizeFrom)
      imageView.layer.position = originFrom

      // Set up fade out effect
      let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
      fadeOutAnimation.toValue = 0.3
      fadeOutAnimation.fillMode = kCAFillModeForwards
      fadeOutAnimation.isRemovedOnCompletion = isRemovedOnCompletion

      // Set up scaling
      let resizeAnimation = CABasicAnimation(keyPath: "bounds.size")
      resizeAnimation.toValue = sizeTo
      resizeAnimation.fillMode = kCAFillModeForwards
      resizeAnimation.isRemovedOnCompletion = isRemovedOnCompletion

      // Set up path movement
      let pathAnimation = CAKeyframeAnimation(keyPath: "position")
      pathAnimation.calculationMode = kCAAnimationPaced;
      pathAnimation.fillMode = kCAFillModeForwards;
      pathAnimation.isRemovedOnCompletion = isRemovedOnCompletion

      // Setting Endpoint of the animation to end animation in last tab use
      let curvedPath = CGMutablePath()
      curvedPath.move(to: originFrom)
      // About curves: https://www.bignerdranch.com/blog/core-graphics-part-4-a-path-a-path/
      curvedPath.addQuadCurve(to: originTo, control: CGPoint(x: originFrom.x,  y: originTo.y))
      pathAnimation.path = curvedPath

      let group = CAAnimationGroup()
      group.fillMode = kCAFillModeForwards
      group.isRemovedOnCompletion = isRemovedOnCompletion
      group.animations = [fadeOutAnimation, pathAnimation, resizeAnimation]
      group.duration = 0.7
      group.setValue(imageView, forKey: "imageViewBeingAnimated")

      imageView.layer.add(group, forKey: "savingAnimation")
   }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X