目录
- 显示层(UIView)动画
- 初级动画
- 关键帧动画
- 逐帧动画
Gif
动画的处理
- 内容层(CALayer)动画
CoreAnimation
结构CABasicAnimation
CAKeyFrameAnimation
CAGroupAnimation
CATransition
CAEmitterLayer
粒子动画CAGradientLayer
梯度动画CAShapeLayer
绘制动画CAReplicatorLayer
复制动画
学习及实践笔记
记录iOS动画的学习及实践
动画的分类
根据动画的实现“位置”,可以分为** 显示层(UIView)动画 ** 和 ** 内容层(CALayer)动画 **
显示层(UIView)动画
初级动画
初级动画,是最为常见的一种动画。通过UIView属性的修改,配合UIView的两种动画apiblock
、方法形式
实现
api使用示范
1 | // 动画方法形式 |
根据简单动画的效果,最常见的显示层(UIView)属性修改分为以下几类
frame
属性相关的位置形状变化alpha
属性相关transform
属性相关
如果希望使用弹跳效果,系统也同样提供了api供开发者使用
1 | + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion |
参数介绍:dampingRatio
:速度衰减比例。取值范围0 ~ 1,值越低震动越强velocity
:初始速度,越大速度越快
关键帧动画
使我们具备控制一段连贯时间内,精确到某个时间点(关键帧)上的连续变化的能力
1 | // 系统api |
api中的options
参数解释 官方文档
1 | UIViewKeyframeAnimationOptionCalculationModeLinear = 0 << 10, // 线性运算模式.The option to use a simple linear calculation when interpolating between keyframe values. |
最后两种Cubic
运算模式,附上实验代码及效果图:(可达鸭眉头一皱,发现这两个参数并不简单)
1 | - (void)viewDidLoad { |
CalculationModeCubic
是一种特殊的贝塞尔曲线(关于贝塞尔曲线原理,文下文中有详细解释),就像官方文档中指出的,通过default Catmull-Rom spline
在关键帧值中插入两个运算点,让贝塞尔曲线能够穿过所有的点而实现运算得出的动画速度更加平滑,防止出现因动画时间过短造成的动画突变的情况
CalculationModeCubicPaced
:暂且解释为均匀平滑模式,可能是因为忽略了时间属性?特殊情况下仍会出现突转的情况
通过实例对比,不管是下落还是突转,cubic
的效果都更加平滑
逐帧动画
- 如果是图片序列的展示,系统提供了序列帧的接口
示例代码:
1 | self.view.backgroundColor = [UIColor lightGrayColor]; |
- 如果用到了图形上下文的绘制,就需要
NSTimer
或者CADisplayLink
实现帧速率的控制,设置好相应的timeinterval
或者frameinterval
即可。(*CADisplayLink
固定刷新率60Hz,frameInterval属性的赋值n,实际间隔时间为n x 1/60`,频率固定,推荐使用 *)
Gif动画
image i/o
Image I/O框架提供了不透明数据类型来读取图像数据和写图像数据到一个目的地(CGImageSourceRef和CGImageDestinationRef)。它支持很多图像格式,包括标准web格式、搞动态范围图像,和原始相机数据。Image I/O还有许多其他特性:
1.mac平台上的最快的图像解码和编码。
2.逐步加载图片的能力。
3.支持图像元数据。
4.缓存效果。
你可以通过下面的对象来创建image source和image destination:
1.URL:即CFURLRef对象。
2.Core Foundation对象:CFDataRef和CFmutableDataRef。
3.Quartz data consumer(CGDataConsumerRef)和data provider(CGDataProviderRef)对象。
1.使用Image I/O框架:#import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>
2、支持的图像格式:例如JPEG、JPEG2000、RAW、TIFF、BMP、PNG。在不同的平台不是所有的格式都支持。你可以调用下列函数来获得支持的格式:
1)CGImageSourceCopyTypeIdentifiers:返回同一类型修饰符的数组,表示支持的图像源
2)CGImageDestinationCopyTypeIdentifiers:返回支持的目的地的Uniform Type Identifiers(UTIs)
** 相关知识链接 **
image i/o
UTType官方文档
UTIs
Gif播放
iOS原生是不支持gif格式图片的,所以当我们想要使用一张gif图片时,需要将gif图片通过image i/o
转换成相应的序列帧图片后,生成UIImage
使用
具体步骤如下图
我们要使用的GIF图片
下面请看我们转换的具体代码:
1 | NSString *path = [[NSBundle mainBundle] pathForResource:@"gakki2" ofType:@"gif"]; |
看一下效果
** 不出所料,果然直接使用是行不通的对吗?但是Gakki还是很好看有木有~ **
我们按照正确的方式打开
1 | /** |
** 注意点: **
- 因为
image i/o
属于Core Foundation
,所以出现了大量的__bridge
处理。书写的时候有点不习惯,不过看一下xcode的错误信息便能知道原因 - 我们的第二步处理,是通过
CGImageSourceRef
读取单帧图片的时间信息。这里需要注意一下,因为我们在用序列帧图片数组合成gif
文件时,需要对每一张图片的该信息赋值 - 操作CF信息的时,最后都要进行
CFRelease
操作 - 仔细看代码的同学也许会注意到下面对于当前帧时间的处理,我们按照
SDWebImage
中相关处理的备注做出解释:1
2
3if (frameDuration < 0.011f) {
frameDuration = 0.1f;
}SDWebImage: UIImage + GIF.m
Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
We follow Firefox’s behavior and use a duration of 100 ms for any frames that specify a duration of <= 10 ms. See rdar://problem/7689300 and http://webkit.org/b/36082 for more information.
看一下我们运行的效果:帧速率的效果跟原图保持一致
序列帧生成Gif
它可以实际应用于视频转Gif的操作,因为视频我们可以按照时间去进行相应的截图,如果控制好时间间隔,循环截图,通过以下方法生成Gif应该可以实现。这里就暂时不实验了,感兴趣的同学可以试一下。
附视频截图方法
1 | // 通过视频url截取封面 |
言归正传,还记得我们前面讲逐帧动画时,用到的图片数组吗?我们利用它们,来合成一张gif被进行沙盒存储
代码实现,此处再次提醒大家需要导入的头文件
1.使用Image I/O框架:#import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>
1 | /** |
在设置gif图片的属性时,我们用到了 色彩空间格式 、 色彩深度 两个参数,色彩格式我们采用最为常见的RGB
格式,色彩深度我们设置为@(8)
。需要大家根据自己的实际需要去设置。
图像类型 | 色阶位数 |
---|---|
黑白图像 | 1 |
灰度图像 | 8 |
彩色图像 | 16 或 32 |
我们看一下沙盒中我们生成的Gif :finder shift + command + g
内容层(CALayer)动画
内容层动画,是指在一定时间内,对layer
的Animatable Property
进行修改所产生的动画。所以,熟悉CALayer
的常见属性,会对我们后边用CoreAnimation
实现** 内容层动画 **大有帮助。
- 下边我们列举一些常见的属性并做了必要备注
- positon
- cornerRadius
- translation.x/y/z
- transform.scale/rotation
- borderWidth/borderColor
- opacity ( like UIView’s alpha )
- shadowOpacity
- shadowRadius ( default is 3.0f )
- shadowOffset ( default is (.0f,-3.0f) )
- backgroundColor
( when we want to change color,we need to use UIColor like this :(__bridge id _Nullable)([UIColor greenColor].CGColor) )
这里需要注意的是,如果一个layer
对象存在对应着的View,则称这个layer
是一个Root Layer
, 非 Root Layer 一般都是通过CALayer
或其子类
直接创建的。
所有的非Root Layer在设置Animatable Properties的时候都存在着隐式动画,默认的duration是0.25秒.
我们看一个简单CAShapeLayer
隐式动画的例子
1 | - (void)testForCALayer{ |
在touchesBegan
方法中调用animationForLayer
的方法,可以发现,我们并没有设置任何动画相关的语句 效果如下
CoreAnimation结构
CABasicAnimation
CABasicAnimation
CABasicAnimation
必要属性的设置:
keyPath
传入layer的属性字符串duration
设置基本动画时长- (void)setRomovedOnCompletion
默认为YES
,会在动画结束时,将动画从render tree
中一次性移除,恢复初始状态。通常我们设置为NO
,保留动画结束时的效果fillModel
属性决定当前对象过了非active时间段的行为,通常我们选择kCAFillModeForwards
kCAFillModeRemoved
这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态kCAFillModeForwards
当动画结束后,layer会一直保持着动画最后的状态kCAFillModeBackwards
同kCAFillModeForwards
是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态kCAFillModeBoth
理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态.
以下三个属性,参照官方描述,很容易理解
fromValue
toValue
byValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/* Father's Love
* The objects defining the property values being interpolated between.
* All are optional, and no more than two should be non-nil. The object
* type should match the type of the property being animated (using the
* standard rules described in CALayer.h). The supported modes of
* animation are:
*
* - both `fromValue' and `toValue' non-nil. Interpolates between
* `fromValue' and `toValue'.
*
* - `fromValue' and `byValue' non-nil. Interpolates between
* `fromValue' and `fromValue' plus `byValue'.
*
* - `byValue' and `toValue' non-nil. Interpolates between `toValue'
* minus `byValue' and `toValue'.
*
* - `fromValue' non-nil. Interpolates between `fromValue' and the
* current presentation value of the property.
*
* - `toValue' non-nil. Interpolates between the layer's current value
* of the property in the render tree and `toValue'.
*
* - `byValue' non-nil. Interpolates between the layer's current value
* of the property in the render tree and that plus `byValue'. */
如果希望加入显示层动画中的spring
效果,Father Apple贴心的为我们准备了CABasicAnimation
的子类CASpringAnimation
,推荐使用。控制弹性动画的属性
damping
initialVelocity
mass
stiffness
简单demo演示
1 | - (void)testForCABasicAnimation{ |
CAKeyFrameAnimation
同显示层动画
中的关键帧动画
类似,提供更加精确的动画控制能力
常用属性
values
数组类型,描述每个关键帧的相关属性keyTimes
NSNumber
泛型数组类型,描述关键帧时间信息,范围
[0,1],时间点:keytime * duration 同addKeyframeWithRelativeStartTime
中的时间描述类似,不再赘述path
属性,更加精确的控制动画的路径
简单demo演示
1 | - (void)callKeyframeAnimation{// CAKeyframeAnimation |
CAGroupAnimation
提供了animations
数组接口,提供多个动画进行组合的能力
简单demo演示
1 | - (void)callAnimationGroup{ |
CATransition
用于不同视图场景之间的切换
使用过程相对简单
- 实例化
CATransition
,设置相应的转场动画key - 设置合适的转场动画属性,比如
duration
,type,subtype
等 - 将动画效果添加到相应视图的
layer
中
系统提供的相关Api(已做必要注释)
公有type Api
1
2
3
4
5
6
7
8
9
10
11
12// 淡入淡出效果
CA_EXTERN NSString * const kCATransitionFade
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
// 移动效果
CA_EXTERN NSString * const kCATransitionMoveIn
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
// 推送效果
CA_EXTERN NSString * const kCATransitionPush
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
// 揭开效果
CA_EXTERN NSString * const kCATransitionReveal
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);私有type Api(只能使用字符串表示 使用有风险,上传需谨慎)
- pageCurl 向上翻页
- pageUnCurl 向下翻页
- cube 立方体翻转
- oglFlip 翻转效果
- stuckEffect 收缩效果
- rippleEffect 水滴波纹效果
- cameraIrisHollowOpen 相机打开效果
- cameraIrisHollowClose 相机关闭效果
subtypes 转场方向
1
2
3
4
5
6
7
8CA_EXTERN NSString * const kCATransitionFromRight
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromLeft
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromTop
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCATransitionFromBottom
CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
简单demo演示
1 | - (void)setImgViewForCATransiton{ |
题外话
- 笔者在之前的文章中,介绍过
CATransition
同NSTimer
结合使用,实现登录界面fade效果的轮播,有兴趣的同学可以看一下《iOS-几行代码实现登录页Fade效果的图片轮播》 - 这里谈到了
Transition
,那么就不得不提一下iOS中的转场动画,之前笔者在这篇文章中,也有所介绍《iOS-自定义转场》
特殊的CALayer动画
CAEmitterLayer 粒子动画
粒子系统,由CAEmitterLayer
粒子发射器图层及CAEmitterCell
粒子发射单元组成
- CAEmitterLayer
1 | @interface CAEmitterLayer : CALayer |
- CAEmitterCell
1 | // 构建方法 |
项目中,粒子动画的应用场景,大多是配合弹框提示出现的
简单demo演示
实现彩带喷撒动画的效果
- 初始化粒子系统,实现基本效果(需要注意的是,Acceleration属性的设置,我们给y方向设置为9.8,热爱物理的童鞋有木有想起什么来呢~~)
1 | - (void)testForCAEmitterLayer{ |
效果如下
- 添加内容层动画,优化效果(因为
EmitterLayer
继承自CALayer
,所以显示层的CoreAnimation
对它同样适用。同时,这里就用到了我们设置的EmitterCell
的name
属性值)
1 |
|
合适位置给发射器添加动画
1 | // 添加发射动画 |
效果如下
CAGradientLayer 梯度动画
CAGradientLayer
是系统提供的颜色梯度变化能力的类,使用相对比较简单
下面看一下头文件中的几个简单接口
1 | // CGColorRef颜色数组 |
项目中似乎用到的地方不多,也许在音频的应用中做些跑马灯(此处手动滑稽)或者正常点的二维码扫描动画中用到。其他的应用场景暂时想不出,如果这篇文章真的有读者的话,欢迎补充交流
简单demo演示
接下来,我们像经典致敬,山寨一个活动解锁效果
直接上代码
1 | // GradientLayer |
效果如下(似乎还蛮不错的-。-)
CAShapeLayer 绘制动画
CAShapeLayer
配合CGPathRef
或者UIBezierPath
路径,堪称绘图利器。上文中演示隐式动画
时已经提到,下面再通过例子,演示几种常见的用法
- UIBezierPath(不了解贝塞尔曲线原理的同学,请移步本文第二章贝塞尔曲线)
- CGPath
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ |
CAReplicatorLayer 复制动画
顾名思义,这个Layer
是让我们具备了复制能力。通过复制一些带动画的layer
,实现有层次感的动画,因为接口较少,且容易理解,特别注意下instanceTransform
属性即可。有兴趣的同学直接进代码
1 | // sound wave |
总结实践
带点击水纹及加载提示的button动画
- 关键点:
- 水波:
NSTimer
逐帧动画 - 位置:显示层动画,修改
frame
及center
- 形状:内容层动画,使用
AnimationGroup
组合cornerRadius
及opacity
的修改 - 旋转:
CADisplayLink
逐帧动画 - 对号:修改
CAShapeLayer
的strokeEnd
属性实现的CABasicAnimation
动画
- 水波:
效果如下:
(demo地址在文章最后)
贝塞尔曲线
原理描述:给定n+1个数据点,p0(x0,y0)…pn(xn,yn),生成一条曲线,使得该曲线与这些点所连接的折线相近
可以看到,上图中折线的中间点,即为我们所说的控制点,他的位置变化,直接影响了曲线的走向
常见案例
可手动移除的动态Badgeview
手机QQ中最早出现(强迫症福音有木有),可以根据手势滑动的距离动态改变badge的形状
我们可以大致根据下图中绘制的样例,理解贝塞尔曲线在这个例子中的作用
这个Badgeview由以下几部分组成:
- 上方跟随手势的小球
- 下方固定位置的小球
- 中间需要填充红色的绘制区域 (注:确定上半部分曲线的三个点为
C/P/B
,确定下半部分曲线的三个点为D/O/A
,这几个点的位置确定,用到了最基础的正弦函数
余弦函数
以及三角形求变长的简单计算)
总结
所有的复杂动画,都是由基本显示层动画
、内容层动画
组合而成。尝试做过一些练习后,笔者这只鶸,发现动画真的是一件有意思的事情呢!
笔者简书地址:iOS-动画知识梳理
传送门:Demo