源码下载:源码
最近在技术群里,有人发了一张带有动画效果的图片。觉得很有意思,便动手实现了一下。在这篇文章中你将会学到Core Animation显式动画中的关键帧动画、组合动画、CABasicAnimation动画。先上一张原图的动画效果。
点击此查看原图动画效果。
本文要实现的效果图如下:
把原动画gif动画在mac上使用图片浏览模式打开,我们可以看到动画每一帧的显示。从每一帧上的展示过程,可以把整体的动画进行拆分成两大部分。
第一部分(Part1)从初始状态变成取消状态(图片上是由横实线变成上线横线交叉的圆)。
第二部分(Part2)从取消状态变回初始状态。
下面我们先详细分析Part1是怎么实现的。根据动画图,把Part1再细分成三步。
Step1 : 中间横实线的由右向左的运动效果。这其实是一个组合动画。是先向左偏移的同时横线变短。先看一下实现的动态效果。
■ 向左偏移—使用基本动画中animationWithKeyPath
键值对的方式来改变动画的值。我们这里使用position.x
,同样可以使用transform.translation.x
来平移。
■ 改变横线的大小—使用经典的strokeStart
和strokeEnd
。其实上横线长度的变化的由strokeStart
到strokeEnd
之间的值来共同来决定。改变strokeEnd
的值由1.0到0.4,不改变strokeStart
的值。横线的长度会从右侧方向由1.0倍长度减少到0.4倍长度。参见示意图的红色区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
-(void) animationStep1{ //最终changedLayer的状态 _changedLayer.strokeEnd = 0.4; //基本动画,长度有1.0减少到0.4 CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeAnimation.fromValue = [NSNumber numberWithFloat:1.0f]; strokeAnimation.toValue = [NSNumber numberWithFloat:0.4f]; //基本动画,向左偏移10个像素 CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"]; pathAnimation.fromValue = [NSNumber numberWithFloat:0.0]; pathAnimation.toValue = [NSNumber numberWithFloat:-10]; //组合动画,平移和长度减少同时进行 CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.animations = [NSArray arrayWithObjects:strokeAnimation,pathAnimation, nil]; animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; animationGroup.duration = kStep1Duration; //设置代理 animationGroup.delegate = self; animationGroup.removedOnCompletion = YES; //监听动画 [animationGroup setValue:@"animationStep1" forKey:@"animationName"]; //动画加入到changedLayer上 [_changedLayer addAnimation:animationGroup forKey:nil]; } |
Step2 : 由左向右的动画–向右偏移同时横线长度变长。看一下Step2要实现的动画效果。其思路和Step1是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
-(void)animationStep2 { CABasicAnimation *translationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"]; translationAnimation.fromValue = [NSNumber numberWithFloat:-10]; //strokeEnd:0.8 剩余的距离toValue = lineWidth * (1 - 0.8); translationAnimation.toValue = [NSNumber numberWithFloat:0.2 * lineWidth ]; _changedLayer.strokeEnd = 0.8; CABasicAnimation *strokeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeAnimation.fromValue = [NSNumber numberWithFloat:0.4f]; strokeAnimation.toValue = [NSNumber numberWithFloat:0.8f]; CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.animations = [NSArray arrayWithObjects:strokeAnimation,translationAnimation, nil]; animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; animationGroup.duration = kStep2Duration; //设置代理 animationGroup.delegate = self; animationGroup.removedOnCompletion = YES; [animationGroup setValue:@"animationStep2" forKey:@"animationName"]; [_changedLayer addAnimation:animationGroup forKey:nil]; } |
Step3: 圆弧的动画效果和上下两个横实线的动画效果。
- 画圆弧,首先想到是使用
UIBezierPath
。画个示意图来分析动画路径。示意图如下:
整个path路径是由三部分组成,ABC曲线
、CD圆弧
、DD′圆
。
使用UIBezierPath
的方法
1 |
- (void)appendPath:(UIBezierPath *)bezierPath; |
把三部分路径关联起来。详细讲解思路。
• ABC曲线
就是贝塞尔曲线,可以根据A、B、C三点的位置使用方法
1 2 3 4 5 |
//endPoint 终点坐标 controlPoint1 起点坐标 //controlPoint2 起点和终点在曲线上的切点延伸相交的交点坐标 - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2; |
二次贝塞尔曲线示意图如下:
其中control point 点是从曲线上取 start point和end point 切点相交汇的所得到的交点。如下图:
首先C点取圆上的一点,-30°。那么,
1 |
CGFloat angle = Radians(30); |
C点坐标为:
1 2 3 |
//C点 CGFloat endPointX = self.center.x + Raduis * cos(angle); CGFloat endPointY = kCenterY - Raduis * sin(angle); |
A点坐标为:
1 2 3 |
//A点 取横线最右边的点 CGFloat startPointX = self.center.x + lineWidth/2.0 ; CGFloat startPointY = controlPointY; |
control point 为E点:
1 2 3 |
//E点 半径*反余弦(30°) CGFloat startPointX = self.center.x + Raduis *acos(angle); CGFloat startPointY = controlPointY; |
• CD圆弧
的路径使用此方法确定
1 |
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise; |
关于弧度问题,UIBezierPath的官方文档中的这张图:
StartAngle 弧度即C点弧度,EndAngel弧度即D点弧度。
1 2 |
CGFloat StartAngle = 2 * M_PI - angle; CGFloat EndAngle = M_PI + angle; |
• DD′圆
的路径和上面2一样的方法确定。
StartAngle 弧度即D点弧度,EndAngel弧度即D′点弧度。