如何使用CAAnimation进行曲线/圆弧动画?

我有一个用户界面,项目被删除,我想模仿iOS邮件中的“移动到文件夹”效果。 将小写字母图标“抛出”到文件夹中的效果。 我的将被倾倒在垃圾箱中。

我尝试在图层上使用CAAnimation实现它。 至于我在文档中可以读到的,我应该能够设置一个byValue并且byValue和CAAnimation应该插值。 我期待做一个小曲线,所以项目通过项目开始位置的上方和左侧的点。

  CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position"]; [animation setDuration:2.0f]; [animation setRemovedOnCompletion:NO]; [animation setFillMode:kCAFillModeForwards]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]]; [animation setFromValue:[NSValue valueWithCGPoint:fromPoint]]; [animation setByValue:[NSValue valueWithCGPoint:byPoint]]; [animation setToValue:[NSValue valueWithCGPoint:CGPointMake(512.0f, 800.0f)]]; [animation setRepeatCount:1.0]; 

我玩了一段时间,但在我看来,Apple意味着线性插值。 添加byValue不会计算出漂亮的弧或曲线,也无法通过它为项目设置动画。

我该怎么做这样的动画?

感谢您提供的任何帮助。

使用UIBezierPath

(如果您使用的是iOS 6或import QuartzCore ,请不要忘记链接然后import QuartzCore

示例代码

您可以使用跟随路径的动画,方便的是, CAKeyframeAnimation支持CGPath ,可以从UIBezierPath 。 斯威夫特3

 func animate(view : UIView, fromPoint start : CGPoint, toPoint end: CGPoint) { // The animation let animation = CAKeyframeAnimation(keyPath: "position") // Animation's path let path = UIBezierPath() // Move the "cursor" to the start path.move(to: start) // Calculate the control points let c1 = CGPoint(x: start.x + 64, y: start.y) let c2 = CGPoint(x: end.x, y: end.y - 128) // Draw a curve towards the end, using control points path.addCurve(to: end, controlPoint1: c1, controlPoint2: c2) // Use this path as the animation's path (casted to CGPath) animation.path = path.cgPath; // The other animations properties animation.fillMode = kCAFillModeForwards animation.isRemovedOnCompletion = false animation.duration = 1.0 animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseIn) // Apply it view.layer.add(animation, forKey:"trash") } 

了解UIBezierPath

Bezier路径(或Bezier曲线 ,准确)的工作方式与您在photoshop,烟花,草图中找到的路径完全相同……它们有两个“控制点”,每个顶点一个。 例如,我刚刚制作的动画:

在此处输入图像描述

像那样工作bezier路径。 请参阅有关细节的文档 ,但它基本上是将“拉”到某个方向的两个点。

画一条路

关于UIBezierPath一个很酷的function是,您可以使用CAShapeLayer在屏幕上绘制它们,从而帮助您可视化它将遵循的路径。

 // Drawing the path let *layer = CAShapeLayer() layer.path = path.cgPath layer.strokeColor = UIColor.black.cgColor layer.lineWidth = 1.0 layer.fillColor = nil self.view.layer.addSublayer(layer) 

改进原始的例子

计算你自己的bezier路径的想法是,你可以使动态完全动态,因此,动画可以根据多个因素改变它将要做的曲线,而不仅仅像我在例子中所做的那样硬编码,例如,控制点可以如下计算:

 // Calculate the control points let factor : CGFloat = 0.5 let deltaX : CGFloat = end.x - start.x let deltaY : CGFloat = end.y - start.y let c1 = CGPoint(x: start.x + deltaX * factor, y: start.y) let c2 = CGPoint(x: end.x , y: end.y - deltaY * factor) 

最后一点代码使得这些点与上图相似,但是在可变量中,相对于点形成的三角形,乘以一个等于“张力”值的因子。

你绝对正确的做法是用CABasicAnimation动画这个位置使它直线前进。 还有另一个名为CAKeyframeAnimation类用于执行更高级的动画。

一系列values

而不是fromValuefromValuebyValue用于基本动画,您可以使用values数组或完整path来确定沿途的值。 如果要先将位置设置为侧面然后向下,则可以传递3个位置的数组(开始,中间,结束)。

 CGPoint startPoint = myView.layer.position; CGPoint endPoint = CGPointMake(512.0f, 800.0f); // or any point CGPoint midPoint = CGPointMake(endPoint.x, startPoint.y); CAKeyframeAnimation *move = [CAKeyframeAnimation animationWithKeyPath:@"position"]; move.values = @[[NSValue valueWithCGPoint:startPoint], [NSValue valueWithCGPoint:midPoint], [NSValue valueWithCGPoint:endPoint]]; move.duration = 2.0f; myView.layer.position = endPoint; // instead of removeOnCompletion [myView.layer addAnimation:move forKey:@"move the view"]; 

如果这样做,您会注意到视图从起点直线移动到中点,而在另一条直线上移动到终点。 缺少使其从中间点开始到结束的部分是更改动画的calculationMode

 move.calculationMode = kCAAnimationCubic; 

您可以通过更改tensionValuescontinuityValuesbiasValues属性来控制它。 如果想要更精细的控制,可以定义自己的路径而不是values数组。

要走的path

您可以创建任何路径并指定该属性应遵循该路径。 在这里,我使用一个简单的弧

 CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y); CGPathAddCurveToPoint(path, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y); CAKeyframeAnimation *move = [CAKeyframeAnimation animationWithKeyPath:@"position"]; move.path = path; move.duration = 2.0f; myView.layer.position = endPoint; // instead of removeOnCompletion [myView.layer addAnimation:move forKey:@"move the view"]; 

试试这个它肯定会解决你的问题:我在我的项目中使用过这个:

 UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(10, 126, 320, 24)] autorelease]; label.text = @"Animate image into trash button"; label.textAlignment = UITextAlignmentCenter; [label sizeToFit]; [scrollView addSubview:label]; UIImageView *icon = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"carmodel.png"]] autorelease]; icon.center = CGPointMake(290, 150); icon.tag = ButtonActionsBehaviorTypeAnimateTrash; [scrollView addSubview:icon]; UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; button.center = CGPointMake(40, 200); button.tag = ButtonActionsBehaviorTypeAnimateTrash; [button setTitle:@"Delete Icon" forState:UIControlStateNormal]; [button sizeToFit]; [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; [scrollView addSubview:button]; [scrollView bringSubviewToFront:icon]; - (void)buttonClicked:(id)sender { UIView *senderView = (UIView*)sender; if (![senderView isKindOfClass:[UIView class]]) return; switch (senderView.tag) { case ButtonActionsBehaviorTypeExpand: { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"]; anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; anim.duration = 0.125; anim.repeatCount = 1; anim.autoreverses = YES; anim.removedOnCompletion = YES; anim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1.0)]; [senderView.layer addAnimation:anim forKey:nil]; break; } case ButtonActionsBehaviorTypeAnimateTrash: { UIView *icon = nil; for (UIView *theview in senderView.superview.subviews) { if (theview.tag != ButtonActionsBehaviorTypeAnimateTrash) continue; if ([theview isKindOfClass:[UIImageView class]]) { icon = theview; break; } } if (!icon) return; UIBezierPath *movePath = [UIBezierPath bezierPath]; [movePath moveToPoint:icon.center]; [movePath addQuadCurveToPoint:senderView.center controlPoint:CGPointMake(senderView.center.x, icon.center.y)]; CAKeyframeAnimation *moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"]; moveAnim.path = movePath.CGPath; moveAnim.removedOnCompletion = YES; CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:@"transform"]; scaleAnim.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; scaleAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; scaleAnim.removedOnCompletion = YES; CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:@"alpha"]; opacityAnim.fromValue = [NSNumber numberWithFloat:1.0]; opacityAnim.toValue = [NSNumber numberWithFloat:0.1]; opacityAnim.removedOnCompletion = YES; CAAnimationGroup *animGroup = [CAAnimationGroup animation]; animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, opacityAnim, nil]; animGroup.duration = 0.5; [icon.layer addAnimation:animGroup forKey:nil]; break; } } 

}

我发现了怎么做。 实际上可以分别为X和Y制作动画。 如果你在同一时间(2.0秒以下)为它们设置动画并设置不同的计时function,它会使它看起来像是从弧线移动而不是从开始到结束值的直线。 要调整弧度,您需要设置不同的计时function。 不确定CAAnimation是否支持任何“性感”计时function。

  const CFTimeInterval DURATION = 2.0f; CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position.y"]; [animation setDuration:DURATION]; [animation setRemovedOnCompletion:NO]; [animation setFillMode:kCAFillModeForwards]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]]; [animation setFromValue:[NSNumber numberWithDouble:400.0]]; [animation setToValue:[NSNumber numberWithDouble:0.0]]; [animation setRepeatCount:1.0]; [animation setDelegate:self]; [myview.layer addAnimation:animation forKey:@"animatePositionY"]; animation = [CABasicAnimation animationWithKeyPath:@"position.x"]; [animation setDuration:DURATION]; [animation setRemovedOnCompletion:NO]; [animation setFillMode:kCAFillModeForwards]; [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; [animation setFromValue:[NSNumber numberWithDouble:300.0]]; [animation setToValue:[NSNumber numberWithDouble:0.0]]; [animation setRepeatCount:1.0]; [animation setDelegate:self]; [myview.layer addAnimation:animation forKey:@"animatePositionX"]; 

编辑:

应该可以使用https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/CAMediaTimingFunction_class/Introduction/Introduction.html(CAMediaTimingFunction in function by functionWithControlPoints ::::)更改计时function。这是一个“三次贝塞尔曲线“。 我相信谷歌有答案。 http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves 🙂

几天前我有一个类似的问题,我用计时器实现它,正如Brad所说,但不是NSTimerCADisplayLink – 这是应该用于此目的的计时器,因为它与应用程序的frameRate同步并提供更流畅和更自然的动画。 您可以在我的答案中查看我的实现。 这种技术确实比CAAnimation提供了更多的动画控制,并没有那么复杂。 CAAnimation无法绘制任何内容,因为它甚至不会重绘视图。 它只会移动,变形和淡化已经绘制的内容。