前言
“工欲善其事,必先利其器”,iOS的Core Animation非常强大,可以做出非常炫酷的动画,但是实际写起来还是比较麻烦,代码不直观,需要反复的改参数、Build、运行,所以诞生了各种动画制作工具,本文就以其中比较有名的QuartzCode为例,快速实现一个收藏功能的星星动画,最终的效果如下:

GitHub地址:https://github.com/zekunyan/QuartzCodeExample-StarAimation
QuartzCode简介
QuartzCode是一款专门用来创作iOS/macOS动画的软件,基本上把整个Core Animation框架的内容都搬到了软件里面,比如各种Layer、Mask、可以用来动画的属性,基本上跟代码是一致的,完全可以当成“可视化动画编程”工具,而且制作以后可以导出代码+资源文件,直接就可以在代码里面调用,方便自定义修改。
以QuartzCode 1.54.0为例,主体界面如下:

简单讲下:
- A: Layers,用于动画的Layer在这里显示,先后顺序、层级和StoryBoard比较像
- B: Inspector,当前选中元素,如Layer的属性编辑,基本和对应CALayer类的属性一一对应
- C: Animations,动画组,可以有多个动画组,每个相互独立
- D: Timeline,动画时间轴,每个Layer的动画都会在时间轴上标出
- E: Keyframe inspector,动画关键帧的属性编辑,也是跟Core Animation的CAAnimation类以及子类的属性一致
- F: 画布,Layer的编辑区
总体上来看,QuartzCode的界面跟XIB、StoryBoard的风格非常类似,还是很容易上手的,更多的教程看官网:http://www.quartzcodeapp.com/
整理思路
首先整理下思路:

总体上可以分成两个部分,一个是动画部分,用QuartzCode实现可视化编辑创建;另一个是功能、代码API设置部分,需要在QuartzCode最后导出的代码基础上,添加、修改,封装成简单的控件库。
QuartzCode实现动画
先来看看动画的实现,本节只把主要步骤流程列出来,详细的参数、设置,参考GitHub上的QuartzCode文件。
显示星星动画
显示星星的动画由三个部分组成,分别是实心的星星从小到大出现,空心的从大到小并透明度减小消失,最后星星四周闪动圆点、短线。
实心星星从小到大出现

- 拖入图片素材到中间的画布上,软件会自动创建一个layer,可以通过Inspector调整大小、位置
- 对Layers里面自动生成的layer重命名
- 点击Animations的“+”按钮,添加一个动画组,命名为show
- 选中当前的layer,点击Timeline下对应layer的“+”按钮,添加一个Bounds帧动画
- Timeline里面的每一行对应帧动画的时间轴,可以在轴上双击、右键添加关键帧,这里默认就好,分别选中Bounds帧动画时间轴两端的关键点,在右边的Keyframe inspector里面设置Size值,起始是(0,0),结束时大小为原图大小
空心星星从大到小、透明度减小消失

基本和上一步一样,可以先隐藏实心的layer以便于观察。
- 添加空心的星星图片素材
- 命名layer
- 给空心的layer添加Bounds和Opacity两种动画,分别修改帧动画的起始、结束值
- 拖动时间轴查看效果
四周闪动短线、圆点
短线、圆点是沿着星星的四周重复排列的,所以这里可以用CAReplicatorLayer来实现,对应到QuartzCode里面就是工具栏的”Replicator”类型的Layer。CAReplicatorLayer可以根据一定的规则重复的绘制内部的子Layer,在这里只需要按照旋转角度变化就好。

- 点击”Replicator”,在中间的画布上拖动出一个layer,然后选择”Shape”里面的圆角矩形,在星星的一角拖动画出一个小的圆角矩形,保证star_layer在replicator layer的内部
- 设置replicator的属性,实现循环重复内部的layer,这里设置”instance count”为5,”Rotation”为72,也就是360/5=72,就可以实现按照星星的五个角重复绘制
- 最后就是添加动画,分别是Opacity透明度从0到1,Position从内到外,Transform变化大小
剩余的圆点,和短线的创建方法一致,就不再重复讲解~
至此,显示的动画就创建好了。
隐藏星星动画
再来看看隐藏星星的动画,有了上一小节的基础,隐藏星星的动画就容易多了,基本步骤都是一样的,如下:

- 添加一个新的动画组hide
- 分别设置每个layer的动画,实心的星星从大到小、同时透明度降低,空心的相反,replicator layer一直隐藏
导出动画代码
动画都实现了好了以后,就可以导出代码了。
依次File->Export Code...,就可以打开导出代码的预览界面:

右边的选项,按照需要选择,默认的基本可以满足要求,“Export Image Assets”最好选上,可以一同导出图片资源。选项的说明参见文档:http://www.quartzcodeapp.com/code-generation/code-options/
至此,最复杂的动画部分就完成了,不用写一行代码,全部可视化编辑~
调整、封装代码
接着就是封装导出的代码,方便使用。
API设计
首先是整个动画的API的设计,为了方便给任意UIView实例添加动画,所以设计成UIView的Category,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @interface UIView (TTGStarAnimation)
- (void)ttg_setupStarAnimation;
- (void)ttg_setupStarAnimationWithEdgeInset:(UIEdgeInsets)inset;
- (void)ttg_showStarAnimated:(BOOL)animated complete:(void (^)())complete;
- (void)ttg_hideStarAnimated:(BOOL)animated complete:(void (^)())complete;
- (BOOL)ttg_getStarIsShow;
@end
|
AssociatedObject保存动画View实例
QuartzCode导出的动画代码都在一个View上,所以为了在Category里给任意UIView实例添加动画View的Property、当前星星是否显示的Bool值,就要用到AssociatedObject,对应的set、get方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
- (TTGStarAnimationView *)ttg_getStarAnimationView { return objc_getAssociatedObject(self, &kTTGButtonStarAnimationViewKey); }
- (BOOL)ttg_getStarIsShow { return [objc_getAssociatedObject(self, &kTTGButtonStarAnimationStateKey) boolValue]; }
- (void)ttg_setStarIsShow:(BOOL)isShow { objc_setAssociatedObject(self, &kTTGButtonStarAnimationStateKey, @(isShow), OBJC_ASSOCIATION_COPY_NONATOMIC); }
|
修改QuartzCode导出的代码 - 暴露内部Layer实例
QuartzCode导出的代码,内部的动画Layer是不可见的,为了能“手动”控制动画View的实际显示状态,就要修改下导出的代码,暴露内部的动画Layer实例。
所有的Layer都在NSMutableDictionary * layers字典里面,通过key获取,给TTGStarAnimationView.h添加三个属性:
1 2 3 4 5 6 7
| @interface TTGStarAnimationView : UIView @property (nonatomic, strong, readonly) CALayer *fillLayer; @property (nonatomic, strong, readonly) CALayer *emptyLayer; @property (nonatomic, strong, readonly) CALayer *replicatedLayer;
@end
|
然后实现getter方法:
1 2 3 4 5 6 7 8 9 10 11 12
| - (CALayer *)fillLayer { return _layers[@"star_animation_fill"]; }
- (CALayer *)emptyLayer { return _layers[@"star_animation_empty"]; }
- (CALayer *)replicatedLayer { return _layers[@"replicator"]; }
|
初始化
ttg_setupStarAnimation和ttg_setupStarAnimationWithEdgeInset:方法,用来初始化动画,包括创建TTGStarAnimationView实例,添加、保存在当前view上,设置每个动画layer的状态等操作:
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 26 27 28 29 30 31 32 33 34
|
- (void)ttg_setupStarAnimation { [self ttg_setupStarAnimationWithEdgeInset:UIEdgeInsetsZero]; }
- (void)ttg_setupStarAnimationWithEdgeInset:(UIEdgeInsets)inset { TTGStarAnimationView *starAnimationView = [self ttg_getStarAnimationView];
if (!starAnimationView) { starAnimationView = [[TTGStarAnimationView alloc] initWithFrame:UIEdgeInsetsInsetRect(self.bounds, inset)]; starAnimationView.userInteractionEnabled = NO; starAnimationView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview:starAnimationView]; objc_setAssociatedObject(self, &kTTGButtonStarAnimationViewKey, starAnimationView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
[starAnimationView removeAllAnimations];
starAnimationView.fillLayer.hidden = YES; starAnimationView.emptyLayer.hidden = NO; starAnimationView.replicatedLayer.hidden = YES; [self ttg_setStarIsShow:NO]; }
|
动画控制
最后就是实现动画的控制,这里比较关键的是对动画的每个Layer的控制,每个Layer的初始、最终状态都要设置,保证多次调用、无论是否执行动画,最终显示都是一致的,以显示星星为例:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
- (void)ttg_showStarAnimated:(BOOL)animated complete:(void (^)())complete { TTGStarAnimationView *starAnimationView = [self ttg_getStarAnimationView]; if (![self ttg_getStarIsShow]) { [self ttg_setStarIsShow:YES]; [starAnimationView removeAllAnimations]; void (^completionBlock)() = ^ { starAnimationView.fillLayer.hidden = NO; starAnimationView.emptyLayer.hidden = YES; starAnimationView.replicatedLayer.hidden = YES; if (complete) { complete(); } };
if (animated) { starAnimationView.fillLayer.hidden = NO; starAnimationView.emptyLayer.hidden = NO; starAnimationView.replicatedLayer.hidden = NO; [starAnimationView addShowAnimationCompletionBlock:^(BOOL finished) { completionBlock(); }]; } else { completionBlock(); } } }
|
隐藏的代码跟显示基本一致,把各种状态、hidden的值倒过来就可以了。
所有代码参见Github。
要写的代码就这么多,是不是非常简单!
总结
“工欲善其事,必先利其器”,借助各种强大的工具软件,做动画的效率简直提升几十倍~
上一篇:在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper
下一篇:开源项目-拼图验证控件TTGPuzzleVerify的实现