ios 如何为视图或图像沿曲线路径的移动设置动画?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1142727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:11:52  来源:igfitidea点击:

How can I animate the movement of a view or image along a curved path?

iphoneobjective-cioscocoa-touchcore-animation

提问by Brad Larson

I am developing a commerce application. When I add an item to the shopping cart, I want to create an effect where an image of the item follows a curved path and ends up at the cart tab.

我正在开发一个商业应用程序。当我将商品添加到购物车时,我想创建一种效果,其中商品的图像沿着弯曲的路径到达购物车选项卡。

How can I create an animation of an image along a curve like this?

如何沿这样的曲线创建图像动画?

回答by Brad Larson

To expand upon what Nikolai said, the best way to handle this is to use Core Animation to animate the motion of the image or view along a Bezier path. This is accomplished using a CAKeyframeAnimation. For example, I've used the following code to animate an image of a view into an icon to indicate saving (as can be seen in the video for this application):

为了扩展 Nikolai 所说的内容,处理此问题的最佳方法是使用 Core Animation 为图像的运动或沿 Bezier 路径的视图设置动画。这是使用 CAKeyframeAnimation 完成的。例如,我使用以下代码将视图图像动画化为图标以指示保存(如该应用程序视频所示):

First of all import QuartzCore header file #import <QuartzCore/QuartzCore.h>

首先导入 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];

回答by Vlad

The way to animate along CGPath using UIView.animateKeyframes(Swift 4)

使用UIView.animateKeyframes(Swift 4)沿 CGPath 设置动画的方法

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)
}

File: Math.Easing.swift

文件: 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
   }
}

File: Math.BezierPath.swift. Look on this SO answer: https://stackoverflow.com/a/50782971/1418981

文件:Math.BezierPath.swift。看看这个答案:https: //stackoverflow.com/a/50782971/1418981

enter image description here

在此处输入图片说明

回答by Nikolai Ruhe

You can animate a UIView's center property using a CAKeyframeAnimation. See the CoreAnimationprogramming guide.

您可以使用 CAKeyframeAnimation 为 UIView 的中心属性设置动画。请参阅CoreAnimation编程指南。

回答by Vlad

Swift 4 version similar to ObjC example from original response.

Swift 4 版本类似于来自原始响应的 ObjC 示例。

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")
   }
}