核心动画可以在layer上创造复杂的动画,比如修改大小、位置、旋转变换等等,也可以同时修改一个或多个属性。
简单修改layer属性做动画
首先理解下隐式动画和显示动画。当修改图层任意属性时都会出发隐式动画,隐式动画有默认时间步调和其他属性。如果不使用系统的默认动画可以通过创建CABasicAnimation对象指定图层的显示动画。调用addAnimation:forKey:方法把动画添加到图层上
在之前的矢量图上添加一个缩放的动画
CAShapeLayer *layer1 = [CAShapeLayer layer];
layer1.bounds = CGRectMake(0, 0, 200, 200);
layer1.position = self.view.center;
layer1.backgroundColor = [UIColor colorWithRed:0.9 green:0.3 blue:0.3 alpha:0.3].CGColor;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 0)];
[path addCurveToPoint:CGPointMake(0, 50)
controlPoint1:CGPointMake(50, 25)
controlPoint2:CGPointMake(25, 50)];
[path addCurveToPoint:CGPointMake(-50, 0)
controlPoint1:CGPointMake(-25, 50)
controlPoint2:CGPointMake(-50, 25)];
[path addCurveToPoint:CGPointMake(0, -50)
controlPoint1:CGPointMake(-50, -25)
controlPoint2:CGPointMake(-25, -50)];
[path addCurveToPoint:CGPointMake(50, 0)
controlPoint1:CGPointMake(25, -50)
controlPoint2:CGPointMake(50, -25)];
CGAffineTransform transform = CGAffineTransformMakeTranslation(100, 100);
[path applyTransform:transform];
layer1.path = path.CGPath;
layer1.fillColor = [UIColor whiteColor].CGColor;
layer1.strokeColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:layer1];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1)];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 0.2, 1)];
animation.duration = 4.0;
[layer1 addAnimation:animation forKey:@"animation1"];
<img src="https://img.haomeiwen.com/i1840221/e8152fe11bd3915a.gif?imageMogr2/auto-orient/strip" width=250>
要注意,显式动画只是提供一个动画,它并不会修改图层对象的数据。所以在动画结束时,需要更新图层对象的数据。
动画会在runloop结束时开始执行,因此当前线程必须要有一个runloop才可以让动画执行。即便改变图层的多个属性或是添加多个动画,他们都会在同一时刻执行。比如你可以在移动的时候同时修改图层的不透明度。虽然他们是同时执行的,但是你可以单独配置每个属性的动画对象,控制他们属性变化的时间节奏。
用关键帧动画修改layer属性
关键帧动画可以在一个动画周期内给属性设置多个值。关键帧动画由一系列的值和每两个值之间变化时的时间节奏组成,他们都用数组表示。
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
keyframeAnimation.keyTimes = @[@(0),@(0.3),@(0.5),@(1)];
keyframeAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1)],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.2, 1.2, 1)],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 0.2, 1)],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.3, 1.2, 1)]];
keyframeAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
keyframeAnimation.duration = 8.f;
keyframeAnimation.fillMode = kCAFillModeForwards;
keyframeAnimation.removedOnCompletion = NO;
[layer1 addAnimation:keyframeAnimation forKey:@"transform2"];
<img src="https://img.haomeiwen.com/i1840221/e58e4631a46e29fd.gif?imageMogr2/auto-orient/strip" width=250>
动画属性keyPath
在第一行创建关键帧动画时调用方法animationWithKeyPath,并传入动画属性keypath。CAPropertyAnimation定义了动画的属性值,它支持很多属性,具体可以查阅官方文档。
关键帧的值
通过动画的path或values来设置关键帧的值。属性是CGPoint类型的(锚点或位置)通常用动画的path属性来设置,path是CGPathRef类型。也可以用values属性来设置,它是数组类型。这时需要把CGPoint类型用NSValue来进行封装再添加到数组中,同样的CATransform3D和CGRect类型用NSValue封装、 CGFloat类型用NSNumber封装。
关键帧之间的节奏
关键帧动画的时间节奏和插值是通过一系列属性共同控制的:
- calculationMode:动画中的计算算法,这个属性同时影响其他属性的使用
- kCAAnimationLinear 或 kCAAnimationCubic,关键帧之间使用直线或圆滑曲线的插值算法实现。 当设置为kCAAnimationCubic时,可通过参数调整差值曲线函数来完全控制中间帧变化方法。
- kCAAnimationPaced or kCAAnimationCubicPaced,使keyTimes和timingFunctions属性失效。动画平滑过渡,也就是匀速进行。
- kCAAnimationDiscrete,使timingFunctions属性失效。动画在关键帧之间跳跃进行,也就是中间帧不进行差值计算,直接从两个关键帧变化。
- keyTimes 指定每一个关键帧的属性值
- timingFunctions 指定每两个关键帧之间过度的时间曲线变化
动画协议
你会发现第一个demo动画结束时,layer回到了初始状态,而第二个demo在开始和结束时layer保持动画开始和结束的状态。因为我设置了fillMode和removedOnCompletion这两个属性。layer的transform值始终都是CATransform3DIdentity,但是设置了这两个属性后可以让图层动画开始时就渲染到CATransform3DMakeScale(1.5, 1.5, 1),结束时渲染到CATransform3DMakeScale(0.3, 1.2, 1)。
CAMediaTiming协议是一套抽象接口,描述了动画的一些信息,CALayer和CAAnimation都实现了该协议
- beginTime 相对于父图层或动画的开始时间
- duration 动画的单次时长
- speed 动画的播放速率,默认是1
- timeOffset 动画的时间偏移,就是从哪里开始播放,默认是0
- repeatCount 动画的重复播放次数,默认是0
- repeatDuration 动画的有效时长,默认是0。结合重复次数使用
- autoreverses 如果是true,动画正播后再进行反播。默认是false
- fillMode 动画的填充模式,这就是上面用到的。可以四种情况:渲染开始和结束维持layer的属性值、在动画结束时layer渲染到动画结束时的属性、在动画开始时layer渲染到动画开始时的属性、开始结束都渲染。
removedOnCompletion是CAAnimation的属性,它表示动画一旦结束渲染就停止,所以第一个demo看到动画结束时layer又变回了初始的样子。
运行时停止动画
动画通常会一直运行到结束,如果想提前中止有两种方法:
- 移除一个动画调用图层的removeAnimationForKey:方法,参数和添加动画时addAnimation:forKey:的第二个参数相同,都不能为nil
- 移除全部动画调用图层的removeAllAnimations方法,它会马上中止所有正在进行的动画,并对layer进行重绘。但这个方法最好不要用!
当从图层移除一个动画的时候,会出发图层的重新绘制。图层会跳跃到动画之前状态,如果想保持最后一帧的状态可以通过修改图层属性实现。
动画组合
如果想图层一直执行多个动画,可以创建CAAnimationGroup对象通过配置animations属性来实现。动画组的的事件节奏和时长属性会覆盖掉所包含的动画属性。
检测动画结束
当动画开始和结束时,可以通过回调得知:
- 对动画事物CATransaction添加setCompletionBlock:方法,在动画结束时调用
- 动画对象实现animationDidStart:和animationDidStop:finished:方法
如果你想连接两个动画尽量不要用这种方式,通过设置动画的beginTime来实现。
网友评论