惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
博客园_首页
H
Help Net Security
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
GbyAI
GbyAI
Scott Helme
Scott Helme
D
Docker
Hacker News: Ask HN
Hacker News: Ask HN
P
Privacy & Cybersecurity Law Blog
Jina AI
Jina AI
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
G
GRAHAM CLULEY
C
Cisco Blogs
The Hacker News
The Hacker News
F
Full Disclosure
Y
Y Combinator Blog
Blog — PlanetScale
Blog — PlanetScale
Recent Announcements
Recent Announcements
G
Google Developers Blog
量子位
K
Kaspersky official blog
Cisco Talos Blog
Cisco Talos Blog
The Cloudflare Blog
A
About on SuperTechFans
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
T
Tenable Blog
P
Palo Alto Networks Blog
H
Heimdal Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
Schneier on Security
Schneier on Security
The Register - Security
The Register - Security
F
Fortinet All Blogs
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
The Blog of Author Tim Ferriss
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
小众软件
小众软件
V
V2EX
爱范儿
爱范儿

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析并模拟实现methodSignatureForSelector方法 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper 开源项目-拼图验证控件TTGPuzzleVerify的实现 Swift开源Mac App - BingWallPaper 有趣的Autolayout示例4-Masonry实现 翻译-为什么objc_msgSend必须用汇编实现 API返回结果设计经验与总结 结合访问Out Parameters出现EXC_BAD_ACCESS的例子,反编译汇编解读__autoreleasing 总结一些iOS项目中组织代码的方法 对组件化与模块化的思考与总结 开源项目-TTGTagCollectionView 有趣的Autolayout示例3-Masonry实现 Swift开源项目: TTGEmojiRate的实现 Swift写的库-TTGSnackbar 有趣的Autolayout示例2-Masonry实现 解决iOS项目的版本兼容问题-结合宏、Category和Runtime 用Runtime的手段填充任意NSObject对象的nil属性 有趣的Autolayout示例-Masonry实现 UITextView编辑时插入自定义表情-续-自定义表情图片的大小 RPC框架Thrift例子-PHP调用C++后端程序 GCD使用经验与技巧浅谈 为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权 Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5 @autoreleasepool-内存的分配与释放 有关宏定义的经验与技巧-简化代码-增强Log Effective-Objective-C-读书笔记-Item-4-如何正确定义常量 UITextView编辑时插入自定义表情-简单的图文混编 关于评论不见了=。= Entity和Model的不同-关于代码的数据层 一次审核被拒的经历-关于iCloud到底应该备份什么数据 Block类型变量-缓存Http请求与回调 提升UITableView性能-复杂页面的优化 NSString的Copy与内存分配 利用NSProxy实现消息转发-模块化的网络接口层设计-原创 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
用QuartzCode快速实现一个收藏动画
土土哥 · 2017-03-08 · via 土土哥的Blog

前言

“工欲善其事,必先利其器”,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文件

显示星星动画

显示星星的动画由三个部分组成,分别是实心的星星从小到大出现,空心的从大到小并透明度减小消失,最后星星四周闪动圆点、短线。

实心星星从小到大出现

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

空心星星从大到小、透明度减小消失

基本和上一步一样,可以先隐藏实心的layer以便于观察。

  1. 添加空心的星星图片素材
  2. 命名layer
  3. 给空心的layer添加Bounds和Opacity两种动画,分别修改帧动画的起始、结束值
  4. 拖动时间轴查看效果

四周闪动短线、圆点

短线、圆点是沿着星星的四周重复排列的,所以这里可以用CAReplicatorLayer来实现,对应到QuartzCode里面就是工具栏的”Replicator”类型的Layer。CAReplicatorLayer可以根据一定的规则重复的绘制内部的子Layer,在这里只需要按照旋转角度变化就好。

  1. 点击”Replicator”,在中间的画布上拖动出一个layer,然后选择”Shape”里面的圆角矩形,在星星的一角拖动画出一个小的圆角矩形,保证star_layer在replicator layer的内部
  2. 设置replicator的属性,实现循环重复内部的layer,这里设置”instance count”为5,”Rotation”为72,也就是360/5=72,就可以实现按照星星的五个角重复绘制
  3. 最后就是添加动画,分别是Opacity透明度从0到1,Position从内到外,Transform变化大小

剩余的圆点,和短线的创建方法一致,就不再重复讲解~

至此,显示的动画就创建好了。

隐藏星星动画

再来看看隐藏星星的动画,有了上一小节的基础,隐藏星星的动画就容易多了,基本步骤都是一样的,如下:

  1. 添加一个新的动画组hide
  2. 分别设置每个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_setupStarAnimationttg_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

要写的代码就这么多,是不是非常简单!

总结

“工欲善其事,必先利其器”,借助各种强大的工具软件,做动画的效率简直提升几十倍~