神刀安全网

干货系列之手把手教你使用Core animation 做动画

源码下载: 源码

最近在技术群里,有人发了一张带有动画效果的图片。觉得很有意思,便动手实现了一下。在这篇文章中你将会学到Core Animation显式动画中的关键帧动画、组合动画、CABasicAnimation动画。先上一张原图的动画效果。

点击此查看 原图动画效果

本文要实现的效果图如下:

干货系列之手把手教你使用Core animation 做动画

实现的效果图.gif

把原动画gif动画在mac上使用图片浏览模式打开,我们可以看到动画每一帧的显示。从每一帧上的展示过程,可以把整体的动画进行拆分成两大部分。

第一部分(Part1)从初始状态变成取消状态(图片上是由横实线变成上线横线交叉的圆)。第二部分(Part2)从取消状态变回初始状态。

下面我们先详细分析Part1是怎么实现的。根据动画图,把Part1再细分成三步。

Step1 : 中间横实线的由右向左的运动效果。这其实是一个组合动画。是先向左偏移的同时横线变短。先看一下实现的动态效果。

干货系列之手把手教你使用Core animation 做动画

step1 Animation.gif

■ 向左偏移—使用基本动画中 animationWithKeyPath 键值对的方式来改变动画的值。我们这里使用 position.x ,同样可以使用 transform.translation.x 来平移。

■ 改变横线的大小—使用经典的 strokeStartstrokeEnd 。其实上横线长度的变化的由 strokeStartstrokeEnd 之间的值来共同来决定。改变 strokeEnd 的值由1.0到0.4,不改变 strokeStart 的值。横线的长度会从右侧方向由1.0倍长度减少到0.4倍长度。参见示意图的红色区域。

干货系列之手把手教你使用Core animation 做动画

stroke示意图.png

-(void) animationStep1{       //最终changedLayer的状态     _changedLayer.strokeEnd = 0.4;     //基本动画,长度有1.0减少到0.4     CABasicAnimation *strokeAnimation = [CABasicAnimationanimationWithKeyPath:@"strokeEnd"];     strokeAnimation.fromValue = [NSNumbernumberWithFloat:1.0f];     strokeAnimation.toValue = [NSNumbernumberWithFloat:0.4f];     //基本动画,向左偏移10个像素     CABasicAnimation *pathAnimation = [CABasicAnimationanimationWithKeyPath:@"position.x"];     pathAnimation.fromValue = [NSNumbernumberWithFloat:0.0];     pathAnimation.toValue = [NSNumbernumberWithFloat:-10];     //组合动画,平移和长度减少同时进行     CAAnimationGroup *animationGroup = [CAAnimationGroupanimation];     animationGroup.animations = [NSArrayarrayWithObjects:strokeAnimation,pathAnimation, nil];     animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn];     animationGroup.duration = kStep1Duration;     //设置代理     animationGroup.delegate = self;     animationGroup.removedOnCompletion = YES;     //监听动画     [animationGroupsetValue:@"animationStep1" forKey:@"animationName"];     //动画加入到changedLayer上     [_changedLayeraddAnimation:animationGroupforKey:nil]; } 

Step2 : 由左向右的动画–向右偏移同时横线长度变长。看一下Step2要实现的动画效果。其思路和Step1是一样的。

干货系列之手把手教你使用Core animation 做动画

step2 Animation.gif

-(void)animationStep2 {     CABasicAnimation *translationAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.translation.x"];     translationAnimation.fromValue = [NSNumbernumberWithFloat:-10];     //strokeEnd:0.8 剩余的距离toValue = lineWidth * (1 - 0.8);       translationAnimation.toValue = [NSNumbernumberWithFloat:0.2 * lineWidth ];       _changedLayer.strokeEnd = 0.8;     CABasicAnimation *strokeAnimation = [CABasicAnimationanimationWithKeyPath:@"strokeEnd"];     strokeAnimation.fromValue = [NSNumbernumberWithFloat:0.4f];     strokeAnimation.toValue = [NSNumbernumberWithFloat:0.8f];       CAAnimationGroup *animationGroup = [CAAnimationGroupanimation];     animationGroup.animations = [NSArrayarrayWithObjects:strokeAnimation,translationAnimation, nil];     animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     animationGroup.duration = kStep2Duration;     //设置代理     animationGroup.delegate = self;     animationGroup.removedOnCompletion = YES;     [animationGroupsetValue:@"animationStep2" forKey:@"animationName"];     [_changedLayeraddAnimation:animationGroupforKey:nil]; } 

Step3: 圆弧的动画效果和上下两个横实线的动画效果。

  1. 画圆弧,首先想到是使用 UIBezierPath 。画个示意图来分析动画路径。示意图如下:
干货系列之手把手教你使用Core animation 做动画

step3 示意图.jpg

整个path路径是由三部分组成, ABC曲线CD圆弧DD′圆

使用 UIBezierPath 的方法

- (void)appendPath:(UIBezierPath *)bezierPath; 

把三部分路径关联起来。详细讲解思路。

ABC曲线 就是贝塞尔曲线,可以根据A、B、C三点的位置使用方法

//endPoint 终点坐标 controlPoint1 起点坐标 //controlPoint2 起点和终点在曲线上的切点延伸相交的交点坐标 - (void)addCurveToPoint:(CGPoint)endPoint           controlPoint1:(CGPoint)controlPoint1           controlPoint2:(CGPoint)controlPoint2; 

二次贝塞尔曲线示意图如下:

干货系列之手把手教你使用Core animation 做动画

二次贝塞尔曲线.png

其中control point 点是从曲线上取 start point和end point 切点相交汇的所得到的交点。如下图:

干货系列之手把手教你使用Core animation 做动画

control point .png

首先C点取圆上的一点,-30°。那么,

CGFloatangle = Radians(30); 

C点坐标为:

    //C点     CGFloatendPointX = self.center.x + Raduis * cos(angle);     CGFloatendPointY = kCenterY - Raduis * sin(angle); 

A点坐标为:

    //A点 取横线最右边的点     CGFloatstartPointX = self.center.x + lineWidth/2.0 ;     CGFloatstartPointY = controlPointY; 

control point 为E点:

    //E点 半径*反余弦(30°)     CGFloatstartPointX = self.center.x + Raduis *acos(angle);     CGFloatstartPointY = controlPointY; 

CD圆弧 的路径使用此方法确定

+ (instancetype)bezierPathWithArcCenter:(CGPoint)centerradius:(CGFloat)radiusstartAngle:(CGFloat)startAngleendAngle:(CGFloat)endAngleclockwise:(BOOL)clockwise; 

关于弧度问题,UIBezierPath的官方文档中的这张图:

干货系列之手把手教你使用Core animation 做动画
弧度.jpg

StartAngle 弧度即C点弧度,EndAngel弧度即D点弧度。

CGFloatStartAngle = 2 * M_PI - angle; CGFloatEndAngle = M_PI + angle; 

DD′圆 的路径和上面2一样的方法确定。

StartAngle 弧度即D点弧度,EndAngel弧度即D′点弧度。

CGFloatStartAngle = M_PI *3/2 - (M_PI_2 -angle); CGFloatEndAngle = -M_PI_2 - (M_PI_2 -angle); 

下面部分代码是所有path路径。

    UIBezierPath *path = [UIBezierPathbezierPath];       // 画贝塞尔曲线 圆弧     [pathmoveToPoint:CGPointMake(self.center.x +  lineWidth/2.0 , kCenterY)];       CGFloatangle = Radians(30);     //C点     CGFloatendPointX = self.center.x + Raduis * cos(angle);     CGFloatendPointY = kCenterY - Raduis * sin(angle);     //A点     CGFloatstartPointX = self.center.x + lineWidth/2.0;     CGFloatstartPointY = kCenterY;     //E点 半径*反余弦(30°)     CGFloatcontrolPointX = self.center.x + Raduis *acos(angle);     CGFloatcontrolPointY = kCenterY;       //贝塞尔曲线 ABC曲线     [pathaddCurveToPoint:CGPointMake(endPointX, endPointY)             controlPoint1:CGPointMake(startPointX , startPointY)             controlPoint2:CGPointMake(controlPointX , controlPointY)];       // (360°- 30°) ->(180°+30°) 逆时针的圆弧 CD圆弧     UIBezierPath *path1 = [UIBezierPathbezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)                                                         radius:Raduis                                                     startAngle:2 * M_PI - angle                                                       endAngle:M_PI + angle                                                       clockwise:NO];     [pathappendPath:path1];     // (3/2π- 60°) ->(-1/2π -60°) 逆时针的圆 DD′圆     UIBezierPath *path2 = [UIBezierPathbezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)                                                         radius:Raduis                                                     startAngle:M_PI *3/2 - (M_PI_2 -angle)                                                       endAngle:-M_PI_2 - (M_PI_2 -angle)                                                     clockwise:NO];       [pathappendPath:path2];       _changedLayer.path = path.CGPath; 

Path路径有了,接着实现动画效果。

圆弧的长度逐渐变长。我们还是使用经典的 strokeStartstrokeEnd 。但是圆弧是如何变长的呢?

(1) 初始圆弧有一段长度。

(2) 在原始长度的基础上逐渐变长,逐渐远离A点,同时要在D点停止。

(3) 长度逐渐变长,最终要在D与D′点交汇。

我们分别解决这个三个问题。

第一个问题, strokeEnd - strokeStart > 0 这样能保证有一段圆弧。

第二个问题,逐渐变长,意味着 strokeEnd 值不断变大。远离A点意味着 strokeStart 的值不断变大。在D点停止,说明了 strokeStart 有上限值。

第三个问题,意味着 strokeEnd 值不断变大,最终值为1.0。

这三个问题说明了一个问题, strokeEndstrokeStart 是一组变化的数据。

那么core animation 中可以控制一组值的动画是关键帧动画( CAKeyframeAnimation )。

为了更准确的给出 strokeEndstrokeStart 值,我们使用 长度比 来确定。

假设我们初始的长度就是曲线ABC的长度。但是贝塞尔曲线长度怎么计算?使用下面方法:

文/Airfei(简书作者) 原文链接:http://www.jianshu.com/p/1e2b8ff3519e# 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。   //求贝塞尔曲线长度 -(CGFloat) bezierCurveLengthFromStartPoint:(CGPoint)starttoEndPoint:(CGPoint) end withControlPoint:(CGPoint) control {     const int kSubdivisions = 50;     const float step = 1.0f/(float)kSubdivisions;       float totalLength = 0.0f;     CGPointprevPoint = start;       // starting from i = 1, since for i = 0 calulated point is equal to start point     for (int i = 1; i <= kSubdivisions; i++)     {         float t = i*step;           float x = (1.0 - t)*(1.0 - t)*start.x + 2.0*(1.0 - t)*t*control.x + t*t*end.x;         float y = (1.0 - t)*(1.0 - t)*start.y + 2.0*(1.0 - t)*t*control.y + t*t*end.y;           CGPointdiff = CGPointMake(x - prevPoint.x, y - prevPoint.y);           totalLength += sqrtf(diff.x*diff.x + diff.y*diff.y); // Pythagorean           prevPoint = CGPointMake(x, y);     }       return totalLength; } 

计算贝塞尔曲线所在的比例为:

CGFloatorignPercent = [self calculateCurveLength]/[self calculateTotalLength]; 

初始的 strokeStart = 0strokeEnd = orignPercent

最终的 stokeStart = ?

//结果就是贝塞尔曲线长度加上120°圆弧的长度与总长度相比得到的结果。 CGFloatendPercent =([self calculateCurveLength] + Radians(120) *Raduis ) / [self calculateTotalLength]; 

实现动画的代码为

    CGFloatorignPercent = [self calculateCurveLength] / [self calculateTotalLength];     CGFloatendPercent =([self calculateCurveLength] + Radians(120) *Raduis ) / [self calculateTotalLength];       _changedLayer.strokeStart = endPercent;       //方案1     CAKeyframeAnimation *startAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"strokeStart"];     startAnimation.values = @[@0.0,@(endPercent)];       CAKeyframeAnimation *EndAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"strokeEnd"];     EndAnimation.values = @[@(orignPercent),@1.0];       CAAnimationGroup *animationGroup = [CAAnimationGroupanimation];     animationGroup.animations = [NSArrayarrayWithObjects:startAnimation,EndAnimation, nil];     animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     animationGroup.duration = kStep3Duration;     animationGroup.delegate = self;     animationGroup.removedOnCompletion = YES;     [animationGroupsetValue:@"animationStep3" forKey:@"animationName"];     [_changedLayeraddAnimation:animationGroupforKey:nil]; 

效果图为:

干货系列之手把手教你使用Core animation 做动画

step3-1 Animation.gif

2.上下横线的动画效果。

此动画效果,需要使用 transform.rotation.z 转动角度。

上横线转动的角度顺序为 0 -> 10° -> (-55°) -> (-45°)
这是一组数据,使用关键帧处理动画。

    CAKeyframeAnimation *rotationAnimation1 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation1.values = @[[NSNumbernumberWithFloat:0],                                 [NSNumbernumberWithFloat:Radians(10) ],                                 [NSNumbernumberWithFloat:Radians(-10) - M_PI_4 ],                                 [NSNumbernumberWithFloat:- M_PI_4 ]                                 ]; 

下横线转动的角度顺序为 0 -> (-10°) -> (55°) -> (45°)

    CAKeyframeAnimation *rotationAnimation2 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation2.values = @[[NSNumbernumberWithFloat:0],                                 [NSNumbernumberWithFloat:Radians(-10) ],                                 [NSNumbernumberWithFloat:Radians(10)  + M_PI_4 ],                                 [NSNumbernumberWithFloat: M_PI_4 ]                                 ]; 

你认为这么就结束了? 最终结束的动画如下:

干货系列之手把手教你使用Core animation 做动画

step3-2 finished Animation.jpg

发现相交的直线没有居中,而是靠左显示。

向左平移,使用 transform.translation.x

    //平移量     CGFloattoValue = lineWidth *(1- cos(M_PI_4)) /2.0; 

即旋转角度又发生偏移量,使用组合动画。

上横线组合动画

    //平移x     CABasicAnimation *translationAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.translation.x"];     translationAnimation.fromValue = [NSNumbernumberWithFloat:0];     translationAnimation.toValue = [NSNumbernumberWithFloat:-toValue];       //角度关键帧 上横线的关键帧 0 - 10° - (-55°) - (-45°)     CAKeyframeAnimation *rotationAnimation1 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation1.values = @[[NSNumbernumberWithFloat:0],                                 [NSNumbernumberWithFloat:Radians(10) ],                                 [NSNumbernumberWithFloat:Radians(-10) - M_PI_4 ],                                 [NSNumbernumberWithFloat:- M_PI_4 ]                                 ];       CAAnimationGroup *transformGroup1 = [CAAnimationGroupanimation];     transformGroup1.animations = [NSArrayarrayWithObjects:rotationAnimation1,translationAnimation, nil];     transformGroup1.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     transformGroup1.duration = kStep3Duration;     transformGroup1.removedOnCompletion = YES;     [_topLineLayeraddAnimation:transformGroup1forKey:nil]; 

下横线组合动画

    //角度关键帧 下横线的关键帧 0 - (-10°) - (55°) - (45°)     CAKeyframeAnimation *rotationAnimation2 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation2.values = @[[NSNumbernumberWithFloat:0],                                 [NSNumbernumberWithFloat:Radians(-10) ],                                 [NSNumbernumberWithFloat:Radians(10) + M_PI_4 ],                                 [NSNumbernumberWithFloat: M_PI_4 ]                                 ];       CAAnimationGroup *transformGroup2 = [CAAnimationGroupanimation];     transformGroup2.animations = [NSArrayarrayWithObjects:rotationAnimation2,translationAnimation, nil];     transformGroup2.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     transformGroup2.duration = kStep3Duration ;     transformGroup2.delegate = self;     transformGroup2.removedOnCompletion = YES;     [_bottomLineLayeraddAnimation:transformGroup2forKey:nil]; 

Part1到此结束。最终效果图

干货系列之手把手教你使用Core animation 做动画

Part1 animation.gif

Part2的思路和Part1思路是一样的。你可以参考代码自己思考一下。核心代码

-(void)cancelAnimation {     //最关键是path路径       UIBezierPath *path = [UIBezierPathbezierPath];     //30度,经过反复测试,效果最好     CGFloatangle = Radians(30);       CGFloatstartPointX = self.center.x + Raduis * cos(angle);     CGFloatstartPointY = kCenterY - Raduis * sin(angle);       CGFloatcontrolPointX = self.center.x + Raduis *acos(angle);     CGFloatcontrolPointY = kCenterY;       CGFloatendPointX = self.center.x + lineWidth /2;     CGFloatendPointY = kCenterY;       //组合path 路径 起点 -150° 顺时针的圆     path = [UIBezierPathbezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)                                                         radius:Raduis                                                     startAngle:-M_PI + angle                                                       endAngle:M_PI + angle                                                       clockwise:YES];       //起点为 180°-> (360°-30°)     UIBezierPath *path1 = [UIBezierPathbezierPathWithArcCenter:CGPointMake(self.center.x,kCenterY)                                                         radius:Raduis                                                     startAngle:M_PI + angle                                                       endAngle:2 * M_PI - angle                                                       clockwise:YES];     [pathappendPath:path1];       //三点曲线     UIBezierPath *path2 = [UIBezierPathbezierPath];       [path2moveToPoint:CGPointMake(startPointX, startPointY)];       [path2addCurveToPoint:CGPointMake(endPointX,endPointY)             controlPoint1:CGPointMake(startPointX, startPointY)             controlPoint2:CGPointMake(controlPointX, controlPointY)];       [pathappendPath:path2];       //比原始状态向左偏移5个像素     UIBezierPath *path3 = [UIBezierPathbezierPath];     [path3moveToPoint:CGPointMake(endPointX,endPointY)];     [path3addLineToPoint:CGPointMake(self.center.x - lineWidth/2 -5,endPointY)];     [pathappendPath:path3];       _changedLayer.path = path.CGPath;       //平移量     CGFloattoValue = lineWidth *(1- cos(M_PI_4)) /2.0;     //finished 最终状态     CGAffineTransformtransform1 = CGAffineTransformMakeRotation(0);     CGAffineTransformtransform2 = CGAffineTransformMakeTranslation(0, 0);     CGAffineTransformtransform3 = CGAffineTransformMakeRotation(0);       CGAffineTransformtransform = CGAffineTransformConcat(transform1, transform2);     _topLineLayer.affineTransform = transform;     transform = CGAffineTransformConcat(transform3, transform2);     _bottomLineLayer.affineTransform = transform;       //一个圆的长度比     CGFloatendPercent = 2* M_PI *Raduis / ([self calculateTotalLength] + lineWidth);       //横线占总path的百分比     CGFloatpercent = lineWidth / ([self calculateTotalLength] + lineWidth);       _changedLayer.strokeStart = 1.0 -percent;       CAKeyframeAnimation *startAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"strokeStart"];     startAnimation.values = @[@0.0,@0.3,@(1.0 -percent)];       //在π+ angle     CAKeyframeAnimation *EndAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"strokeEnd"];     EndAnimation.values = @[@(endPercent),@(endPercent),@1.0];       CAAnimationGroup *animationGroup = [CAAnimationGroupanimation];     animationGroup.animations = [NSArrayarrayWithObjects:startAnimation,EndAnimation, nil];     animationGroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];     animationGroup.duration = kStep4Duration;     animationGroup.delegate = self;     animationGroup.removedOnCompletion = YES;     [animationGroupsetValue:@"animationStep4" forKey:@"animationName"];     [_changedLayeraddAnimation:animationGroupforKey:nil];       //平移x     CABasicAnimation *translationAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.translation.x"];     translationAnimation.fromValue = [NSNumbernumberWithFloat:-toValue];     translationAnimation.toValue = [NSNumbernumberWithFloat:0];       //角度关键帧 上横线的关键帧  (-45°) -> (-55°)-> 10° -> 0     CAKeyframeAnimation *rotationAnimation1 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation1.values = @[[NSNumbernumberWithFloat:- M_PI_4 ],                                   [NSNumbernumberWithFloat:- Radians(10) - M_PI_4 ],                                   [NSNumbernumberWithFloat:Radians(10) ],                                   [NSNumbernumberWithFloat:0]                                   ];       CAAnimationGroup *transformGroup1 = [CAAnimationGroupanimation];     transformGroup1.animations = [NSArrayarrayWithObjects:rotationAnimation1,translationAnimation, nil];     transformGroup1.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     transformGroup1.duration = kStep4Duration;     transformGroup1.removedOnCompletion = YES;     [_topLineLayeraddAnimation:transformGroup1forKey:nil];       //角度关键帧 下横线的关键帧  (45°)-> (55°)- >(-10°)-> 0     CAKeyframeAnimation *rotationAnimation2 = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"];     rotationAnimation2.values = @[[NSNumbernumberWithFloat: M_PI_4 ],                                   [NSNumbernumberWithFloat:Radians(10) + M_PI_4 ],                                   [NSNumbernumberWithFloat:-Radians(10) ],                                   [NSNumbernumberWithFloat:0]                                   ];       CAAnimationGroup *transformGroup2 = [CAAnimationGroupanimation];     transformGroup2.animations = [NSArrayarrayWithObjects:rotationAnimation2,translationAnimation, nil];     transformGroup2.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseOut];     transformGroup2.duration = kStep4Duration;     transformGroup2.delegate = self;     transformGroup2.removedOnCompletion = YES;     [_bottomLineLayeraddAnimation:transformGroup2forKey:nil];   } 

最终效果图:

干货系列之手把手教你使用Core animation 做动画

finished animation.gif

本篇文章讲解结束!

代码点此链接下载: https://github.com/WZF-Fei/ZFChangeAnimation

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 干货系列之手把手教你使用Core animation 做动画

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮