来来来,今天咱们通过实现一个类似Twitter的启动动画来看看CAKeyFrame Animation和CAAnimation Group怎么玩。
所以今天咱们的重点到了第七章,CAKeyFrame Animation和CAAnimation Group。最后的那个启动动画完全是为了实践一下看看CAKeyFrame Animation和CAAnimation Group怎么使用。
有读者私下说更新速度太慢了。在码云上看了一下下载的统计,发现其实下载的童鞋并不是特别多。如果只是看看思路,或者复习一下这些基础知识,确实是很快。但是如果对于这些内容不是特别熟悉,建议还是敲一边代码,看看自己能碰到什么坑。
俺写一篇分享文章大约要4~6个小时,大体是三部分:想到合适的例子,敲代码写注释,写文章。通常都会看自己当前的情况,决定是先写swift版还是OC版,然后不动脑子的翻译成另外一版调整一下BUG。这样也是为了训练自己,前段时间发现自己有时候会不自觉的把两种语言混在一起,这个习惯特别不好,所以想用这种方式自己纠正一下。到最后更新写文章的时候反而更轻松了,因为不用动脑。哈哈~
Come on~下面这张图纯粹是为了简书当作封面使用的。也不知道为什么,以前简书还能自动把GIF的第一桢当作封面,现在不好使了。
CAKeyFrame Animation和CAAnimation Group.png
下面展示一下写完之后的成果:
ani.gif
源代码可以在这里下载,里面有OC和Swift两版。https://git.oschina.net/atypical/CAKeyFrame-_Group_Animation.git
iOS动画系列之CAKeyFrame Animation和CAAnimation Group(OC和Swift两版)
1. CAKeyframeAnimation
CAKeyframeAnimation是CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值。
创建步骤:
- 创建关键帧动画对象
- 设置属性
- 添加到要作用的layer上
- 如果使用rect椭圆的方式,动画会不连贯,停顿一下。原因是因为矩形的周长比椭圆的长,动画路径按照椭圆执行完之后,需要等待一下最大周长走完。 这些都是因为计算模式导致的。
1.1 创建一个抖动的小方块
我们用一个简单的demo来感受一下CAKeyframeAnimation,来做一个会抖动的小方块。
抖动的小方块.gif
代码语言:javascript复制 CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
//设置一些列的关键帧动画
animation.values = @[@(-M_PI_4 / 5),@(M_PI_4 / 5),@(-M_PI_4 / 5)];
animation.repeatCount = CGFLOAT_MAX;
[self.view.layer addAnimation:animation forKey:@"rotation"];
1.2 创建一个沿椭圆路径运动的小飞机
我们创建一个UIBezierPath,让小飞机沿着这个路径运动。
代码语言:javascript复制//创建动画对象
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//设置属性:创建beziper路径,并把路径作为运动轨迹
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 500)];
keyFrameAnimation.path = bezierPath.CGPath;
//设置动画时间
keyFrameAnimation.duration = 2;
//设置动画循环播放的次数
keyFrameAnimation.repeatCount = CGFLOAT_MAX;
//设置动画的计算模式
keyFrameAnimation.calculationMode = kCAAnimationPaced;
//将动画添加到layer上
[self.planeView.layer addAnimation:keyFrameAnimation forKey:nil];
1.3 动画叠加
刚才添加了一个沿路径运动的飞机,我们同时还可以给飞机再把抖动的那个动画也添加上去。前几篇提到后面那个forKey
,可能还有童鞋不知道干啥用。现在看到了木有?一个layer里面好几个动画,如何找到对应的动画吶?现在通过这个key就能找到了。
// 为小飞机同时添加抖动的动画和椭圆路径旋转的动画
[self.planeImageView.layer addAnimation:[self shakeAni] forKey:nil];
[self.planeImageView.layer addAnimation:[self ovalAni] forKey:nil];
2. CAAnimationGroup
单一的动画在实际中往往是不能满足需求的,这时就需要用到动画组。
- 是CAAnimation的子类
- 可以保存一组动画对象,将CAAnimationGroup对象加入图层后,组中所有动画对象可以同时并发运行.
我们试着做一个包行旋转、缩放、按一定弧度路径组合在一起的动画。效果如下:
arcAni.gif
代码语言:javascript复制- (CAAnimationGroup *)groupAni{
// 实例化一个组动画对象
CAAnimationGroup *groupAni = [[CAAnimationGroup alloc] init];
// 创建旋转的动画
CABasicAnimation *basicRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
basicRotation.toValue = @(M_PI * 2);
// 创建缩放的动画
CABasicAnimation *basicScale = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
basicScale.toValue = @(0.2);
// 创建按照路径移动的动画
CAKeyframeAnimation *keyFrameAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *arcPath = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:150 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
keyFrameAni.path = arcPath.CGPath;
keyFrameAni.calculationMode = kCAAnimationPaced;
keyFrameAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// 将旋转、缩放、移动的动画添加到组动画中
groupAni.animations = @[basicRotation,basicScale,keyFrameAni];
// 组动画重复次数
groupAni.repeatCount = CGFLOAT_MAX;
// 组动画时长
groupAni.duration = 2;
return groupAni;
}
3. 实现类似Twitter的启动动画
3.1实现思路
1,在View上设置一个东西能够遮挡住背景图; 2,把遮罩变成五角星; 3,让遮罩慢慢变大,中间可见区域越来越大。
yes!思路就是这样的。那怎么遮住背景图片呢?
3.2 CALayer的遮罩属性
CALayer本身有一个属性,叫mask。我们来看一下官方解释:
代码语言:javascript复制@property(nullable, strong) CALayer *mask;
When true an implicit mask matching the layer bounds is applied to
the layer (including the effects of the `cornerRadius' property). If
both `mask' and `masksToBounds' are non-nil the two masks are
multiplied to get the actual mask values. Defaults to NO.
Animatable.
它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于其他能够在父图层中绘制出图像的子图层,mask图层定义了父图层的部分可见区域。
mask图层的Color属性是无关紧要的,真正重要的是图层的轮廓。也就是说mask图层实心的部分会被保留下来,其他的则会被抛弃。如果mask图层比父图层要小,只有在mask图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。
Paste_Image.png
3.3 实现类似Twitter的启动动画
好了准备工作都做完了,我们就开始写这个动画了。这个动画其实就是一个简单的CAKeyframeAnimation。设置了三个关键帧动画的大小,以及这三个关键帧的运动节奏。
然后,就好啦~然后,就好啦~然后,就好啦~然后,就好啦~
哪尼?!!!就这样?!!对啊,就这样。
代码语言:javascript复制 - (CAKeyframeAnimation *)maskAni{
// 放大缩小视图,keypath使用bounds
CAKeyframeAnimation *maskAni = [CAKeyframeAnimation animationWithKeyPath:@"bounds"];
// 动画时间
maskAni.duration = 30.75;
// 动画延迟0.5秒播放
maskAni.beginTime = CACurrentMediaTime() 0.5;
// 设置关键帧动画的数值
CGRect startRect = self.maskLayer.frame;
CGRect tempRect = CGRectMake(0, 0, 100, 100);
CGRect finalRect = CGRectMake(0, 0, 2000, 2000);
maskAni.values = @[[NSValue valueWithCGRect:startRect],[NSValue valueWithCGRect:tempRect],[NSValue valueWithCGRect:finalRect]];
// 设置关键帧动画的运动节奏
maskAni.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// 动画播放结束后是否移除动画
maskAni.removedOnCompletion = NO;
// 动画填充模式:kCAFillModeForwards:当动画结束后,layer会一直保持着动画最后的状态
maskAni.fillMode = kCAFillModeForwards;
return maskAni;
}
留一个小问题:
我在OC和Swift里面对不同的View使用了mask。一个是给背景图片的UIImageView设置了mask,另一个是直接给Controller的View设置了mask。设置这两个有神马区别咩?
好,下篇其实有一个重头,就是CAShapeLayer。因为在工作中碰到的大部分动画都是通过UIView的动画block实现,其他都基本上都是需要用到CAShapeLayer。我们下次玩点好玩的吧~ 如果还有兴趣,可以看看本系列的其他文章哈。