《iOS三问》 -- 从动画系统的实现谈iOS核心动画

Core Animation库是iOS动画技术的基础,由一系列类与子类组成(他们基本都有个特点就是以各种CA开头,如CALayer,CAAnimation)。我们之前学习到的View动画,隐式Layer动画等,都是基于Core Animation库的一个封装实现。Core Animation又称显式动画,使用显式动画技术,我们可以更细致地定义我们要的动画的整个实现过程。

通过本章,我希望和大家一起不仅掌握iOS的动画编程,更掌握UI系统关于动画编程的核心技术与探索核心技术的方法。

学习显式动画,可以揭示iOS动画库实现的整个细节,他的设计思想与实现。

据我的总结,一般UI系统都支持如下几种动画:

第一种是 帧动画(Frame Animation)。这是最简单的动画,就是把动画拆成一帧帧图片连续播放,从而形成的动画。我们在Image Animation已经学习过了;

第二种是 属性动画(Property Animation)。这是最常见的动画方式,我们在Animation概述中介绍过,就是把动画的本质定义为视图某些属性随时间的变化。这个也是我们今天学习的主要动画方式。

第三种是 布局动画(Layout Animation),就是当布局时其子视图展示出来的动画,比如你在桌面移动图标时,你移动一个图标,后面的图标会以动画的形式后移让开一个空。这种动画其实就是基于布局事件与属性动画复合处理而成,并不是新的动画系统。

第四种是 转场动画(Transition Animation),通常用于一个视图到另一个视图的转换。比如A视图渐隐B视图渐现这样,或B视图从边上推进来盖住A视图,其实也是属性动画的复合实现。

第五种就是 视频动画。其实就是播放视频代替动画,这个就不多说了

当然因为本人对游戏编程,对unity3d等技术不熟,还有一些物理引擎动画,通过模拟现实生活中的物理动作实现的动画等,因为我对此不熟,就不介绍了,等我后面学习到再作个补充。

本章主要介绍显示属性动画,这也是Core Animation动画的核心。

1.1 让我们来设计一套属性动画系统

如果让我们来设计一套简单的属性动画系统,根据我们在动画概述里学习到的,主要要有这几个部分组成:

  1. 计算 - 对属性的计算,比如某时刻下可动画属性的值;
  2. 计时 - 对象属性随时间是个怎样的变化;
  3. UI刷新 - 属性变化后怎样刷新UI;
  4. 线程管理 - 当然,总不可以在主线程绘制动画吧,驱动动画的子线程我们还要加以管理。

由此,我们可以作出最简单的动画系统:

最简单的动画系统

我们看下这套系统:

  1. XXLayer负责UI的显示与重绘,我们假设动画针对layer的一个属性propertyX,则它的重绘方法会依赖propertyX绘制出当前的效果;
  2. XXAnimationThreadXXAnimation负责动画线程管理,我们用XXAnimation封装一个动画,它包括了一个属性动画需要的基本参数,表示在duration时间段时,属性值从from值过渡到to值。XXAnimationThread提供线程管理与计时功能,开启动画后,每隔一段时间利用XXAnimation的参数与计时方法算出属性当前值,并调用XXLayerredraw方法刷新界面;
  3. XXAnimation中的timingFunction负责最核心的计算功能,其

我们用伪代码剖析一下最简单动画系统的实现,主要细节在于XXAnimationThreadstartAnimation方法:

计时方法
1
2
动画线程实时计算与刷新方法
1
2
3
4
5
6
7
8
9
10
11
12
void ()
{
long currentTime = getCurrentTime();
while(currentTime <= (animation.beginTime + animation.duration)) {
let propertyX_value = animation.timingFunction(currentTime);
layer.propertyX = propertyX_value;
layer.redraw();
}
}

2 转场动画(Transition Animation)

在之前介绍View Animation时我们介绍过过转场动画

2.1 图层树的转场动画

CATransition可以对图层树做动画,这个是非常强大的(个人猜测这个应该是对整个图层树的cache image做动画实现的)。这意味着你可以不用考虑图层树的细节,直接在layer根部使用transition animation,方便地启用动画。

比如我们下面的操作想在tab切换时展示页面转换的渐隐渐现动画:

tab切换时展示页面转换的渐隐渐现动画
1
2
3
4
5
6
7
8
9
- (void)tabBarController:(UITabBarController *)tabBarController didSelectVie
{
// 创建转场动画对象
CATransition *transition = [CATransition animation];
transition.type = kCATransitionFade;
// 应用于tab图层根部
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
}

2.2 CAMediaTiming 协议

CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayerCAAnimation 都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。

  • 一个动画迭代

CAMediaTiming中定义的一些属性(也可以称之为参数),给要进行的动画的一次迭代指定了时间与计时的参数。比如durationrepeatCount这两个参数,duration表示动画一个迭代的持续时间repeatCount表示整个完 大专栏  《iOS三问》 -- 从动画系统的实现谈iOS核心动画整动画包含多少次迭代。,如果duration=2repeatCount=3,就意思着整个动画时长为2*3=6秒。

durationrepeatCount的默认值为0,分别代码0.25秒1次迭代。把repeatCount设为INFINITY表示无限迭代;

  • timeOffset和beginTime

timeOffsetbeginTime类似,但是和增加beginTime导致的延迟动画不同,增加 timeOffset 只是让动画快进到某一点,例如,对于一个持续1秒的动画 来说,设置 timeOffset为0.5意味着动画将从一半的地方开始。

  • 其它参数

speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个 duration 为1的动画,实际上在0.5秒的时候就已经完成了。

此外,还有autoreverse,会自动生成从tofrom的反向动画等参数,具体罗列在文章头部的UML图中。

最后值得一说的就是fillMode,它是一个 NSString 类型,表示当动画播放完后保持在一个什么状态。我们都之前说过,一般来说,动画播放完就被remove掉了。但是如果你设置了removeOnCompletionNO,就会保持在动画的一个状态,而fillMode就是控制它展示什么状态。其可选属性:

1
2
3
4
kCAFillModeForwards
kCAFillModeBackwards
kCAFillModeBoth
kCAFillModeRemoved

kCAFillModeRemoved为默认值,表示动画不再播放时显示图层当前模型中的值。而forwards,backwards,both表示展示为动画开始时或结束时的状态。这样可以避免动画被中断,或因各种原因在结束时突然地变到一个状态去,要注意的一点就是,需要把 removeOnCompletion 设置为 NO

3 引用

【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]
【2】[Nick LockWood - 《iOS Core Animation: Advanced Techniques》]

CAAnimation定义了动画的基本属性,在上面的类图中我用空行大致分了段,有+ (instancetype)animation这样的方便创建动画对象的实例方法,设置动画回调对象delegate;有与动画计时相关的beginTime,timeOffset,duration等,还可以指定计时方法timingFunction,以及动画的额外控制autoreversesrepeatCount等。另外CAAnimation实现了KVC,也就是你可以像字典一样对特定的key赋值,这点后面会说到。

CAPropertyAnimation更进一步,对动画所对应的动画属性做了定义,keyPath对应于要产生动画的CALayer动画属性。additive表示动画结束后的属性值将做为layer的当前值(即把layer的当前呈现值赋给模型值,参考动画基础中的’呈现与模型’小节理解下)。

CABasicAnimation则是动画继承结构中较常用到的实现类了,使用它可以定义一个具体的基本的动画行为。比如fromValuetoValue表示一个可动画的属性从一个值渐变到另一个值。byValue表示从动画属性值的变化量,比如fromeValue=0,byValue=5,则表示可动画属性从0变化到5. 而如果只设置了byValue,则表示可动画属性是从属性的当前值变化到当前值+byValue。到这里你就知道了,使用byValue, 其实就是让系统帮你计算toValue,动画的本质还是属性值从fromValue变化到toValue

3.1 使用CABasicAnimation

使用CABasicAnimation时首先要注意下呈现与模型问题。因为我们直接修改layer的值时改的是模型层的值,直到渲染周期来临时执行动画逻辑此时呈现层属性值才会从fromValue变到toValue。而动画结果后,动画对象会被干掉,此时呈现层会呈现模型层当前的属性值。也就是如果你修改的layer的值与动画toValue不一致的话,动画结果后layer会突然变到你改变的值,这样对用户来说很不友好。所以,一般我们需要保证动画结束后的值与当前模型层的值相等。

一种方法是省去fromValuetoValue值,因为系统会自动帮你计算。参考动画基础中的’呈现与模型’小节理解下,我们说Layer有呈现层(presentationLayer)模型层(moduleLayer),当我们改变layer的值时,先是改变其模型层的值,直到下一次渲染周期到来时触发动画才开始变化呈现层的属性值。而这时呈现层的当前值也就是你改变Layer属性值之前的值,也就是动画的fromValue。而要变化到的值也就是toValue则是模型层当前的值。如果这样说不好理解可以参考下下面的示例(假设animLayer当前x值为100):

省去fromValue与toValue的BaseAnimation
1
2
3
4
5
6
7
8
9
10
11
12
13
[CATransaction setDisableActions:YES]; // 去掉隐式动画
CGPoint newPosition = animLayer.position;
newPosition.x = 200;
animLayer.position = newPosition; // 改变layer的属性值,将在下个渲染周期呈现
// -- 构造CABasicAnimation
// 这里不用设置fromValue与toValue(下面的注释部分)
// 系统值自动将fromValue设为position修改之前的值,toValue设为修改之后的值(200)
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
anim.duration = 1;
// anim.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 50)];
// anim.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 50)];
// -- 开启动画
[animLayer addAnimation:anim forKey:nil];

上面动画开启后, animLayer将会从x=100处移动x=200,然后停在那里。

还有另一种方法是使用addtive,这样就可以省去在添加动画之前修改CALayer模型层的属性值。因为如上文所述,动画结束后会将呈现层(presentationLayer)的属性值同步到模型层(moduleLayer)上。比如同样是实现上面的layer从0移动200处的动画

1
2
原文地址:https://www.cnblogs.com/lijianming180/p/12409894.html