浅谈Layer和Animation

iOS中的动画默认是指Core Animation,当然还有第三方的比如Facebook的Pop等。Core Animation是作用在图层Layer上的,所以本文分别介绍LayerAnimation

Layer 与 View

View与Layer关系

在iOS中,每一个UIView背后都有一个Layer,这个我们可以通过view.layer获得。而ViewLayerdelegate。这个delegate是这样定义的:

@interface NSObject (CALayerDelegate)
...

/* If defined, called by the default implementation of the
 * -actionForKey: method. Should return an object implementating the
 * CAAction protocol. May return 'nil' if the delegate doesn't specify
 * a behavior for the current event. Returning the null object (i.e.
 * '[NSNull null]') explicitly forces no further search. (I.e. the
 * +defaultActionForKey: method will not be called.) */

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

@end

Layer是真正做显示和动画的,而View是一种高级封装,并提供用户交互功能。

**Q: **既然每个View都有一个Layer,为什么要分开成两种对象呢,为什么不把这些功能全部放到View本身去?
A: 这是苹果为了跨平台考虑的。 因为iOS和Mac OS 对用户的交互处理是有很大区别的,一个是多点触控,一个是键盘鼠标。而两者对于界面元素显示和动画处理确是相似的。这样,将Layer分离出来可以起到职责分离、代码复用的作用,同时也方便第三方库的开发者。

大家也许注意到了,图中的Layer标记为Root Layer。我们可以把View本身携带的既创建View时创建的Layer称为Root Layer,相反,把那些单独的Layer称为非Root Layer

Q: Root Layer 和 非 Root Layer有什么区别?
A: 改变一个非Root Layer的可做动画属性(Animatable Property)时,属性值从起点到终点有一个平滑过渡的过程,既隐式动画,默认时长是0.25秒。而改变一个Root Layer的可做动画属性时,是直接改变的,没有动画的。我们可以用下面的代码演示改变两种layer的颜色。

- (void)changeColor {
    [CATransaction begin];
    //为了方便观察,将时长改为2秒
    [CATransaction setAnimationDuration:2.0];
    
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    //改变 非Root Layer的背景色 会有隐式动画
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    //改变 Root Layer的背景色 没有隐式动画
    self.colorView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

    [CATransaction commit];
}
改变颜色动画1

当然,要想让Root Layer改变颜色时有动画也是办法的,我们只需要把它放在一个block中。

//在block中, 改变 Root Layer的背景色 会有隐式动画
[UIView animateWithDuration:2.0 animations:^{
  self.colorView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}]; 
改变颜色动画 2

这又是为什么呢?

其实,官方文档已经对此有简单的说明。

The UIView Class disables layer animation by default but reenables them inside animation blocks.

继续死磕,会发现原因跟上面提到的CALayerDelegate里面的actionForLayer:forKey方法有关。这个方法有三种返回结果:

  1. 返回非空值,既某种行为。这样就是动画效果。
  1. 返回nil,不做什么行为,继续去其他地方寻找合适的actions。
  2. 返回Null,停止寻找。

至此,我们知道了根因,就是默认情况下,UIview的actionForLayer:forKey方法返回nil。而在block中时,返回一个非空值。

Layer Tree

图层树状结构以及对应的视图层级

每一个视图都有一个父视图以及若干个子视图,这形成了一个树状的层级关系。对应地,每个视图的图层也有一个平行的层级关系,称之为图层树(Layer Tree)。直接创建的或者通过UIView获得的(view.layer)用于显示的图层树,称之为模型树(Model Tree),模型树的背后还存在两份图层树的拷贝,一个是呈现树(Presentation Tree),一个是渲染树(Render Tree)

模型树则可以通过modelLayer属性获得,而呈现树可以通过模型树的layer.presentationLayer获得。模型树的属性值就是我们看到的动画起始和结束时的值,是静态的;呈现树的属性值和动画运行过程中界面上看到的是一致的,是动态的。而渲染树是私有的,你无法访问到,渲染树是对呈现树的数据进行渲染,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,所以你会发现Animation的动画并不会阻塞主线程。

Layer Property

Layer有很多属性,这里强调两个属性:

anchorPoint

锚点是按照layer的bounds比例取值的,其值是左上角(0,0)到右下角(1,1),默认是(0.5, 0.5)既中心点。形象地,我们可以认为是动画(平移、缩放、旋转)的支点。
下面的动画演示了使用默认的锚点(0.5,0.5)和(0,1)的区别。

默认锚点(0.5,0.5),铅笔的中心点在路径上移动
锚点改为(0,1.0),铅笔的笔尖在路径上移动

position

position有点类似于UIView的center,但不总是中心点,它是anchorPoint相对于父layer的位置。所以layer的frame不变时,改变anchorPoint也会改变position的值;同样,position不变时,改变anchorPoint值也会改变frame的origin的值。

anchorPoint与position关系

Animations

动画有隐式动画显式动画之分。前面我们以及介绍了隐式动画的原理了,接下来主要讲显式动画
显式动画就是我们在layer上调用了addAnimation:forKey方法。这里一旦一个layer添加了一个动画时,就拷贝了一份Animation对象,所以接下来的对Animation对象的修改只会对后面添加的layer起作用。

动画的基类是CAAnimation,它与各派生类的关系见下图:

CAAnimation 继承关系

我们看一个简单的CABasicAnimation的例子,平移一个圆块。

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @0;
animation.toValue = @200;
animation.duration = 1;
[circle.layer animation forKey:@"basic"];
basicAnimation

我们发现,动画结束后立马回到原点。 这就牵扯到我们前面提到的Model TreePresentation Tree了。

圆块移动过程中,改变的是Presentation Tree的layer属性值,而Model Tree的layer值没变,动画结束时默认是删除的,所以又变回Model Tree的layer属性值了,既回到原点。解决办法有两个:

  • 动画结束后,手动修改Model Tree的layer属性值
circle.layer.position = CGPointMake(200, 220);
  • 动画结束时不删除
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;

两种方案都可以使圆块保持在最末端,但推荐第一种,因为第二种没有删除动画,会浪费渲染资源,而且也造成Model TreePresentation Tree不同步。

时间系统

CAMediaTiming是一个协议,控制了动画运行时间相关的系数。CALayerCAAnimtion都实现了这个协议。它有一些重要的参数。

  • beginTime
    动画开始的延迟时间,相对于父layer的时间。一般取值为
   layer.beginTime = CACurrentMediaTime() + 延迟的秒数
  • speed
    动画执行的速度,有叠加效果。比如layer的速度是2,父layer的速度是2,那这个layer上动画执行的速度就是4。speed还可以是负值,这会导致动画反向执行。

  • repeatCount

动画执行的次数,可以为小数,0.5代表动画执行一般就结束。

  • repeatDuration

动画重复的时长,可以比duration小,那就中途结束。

  • timeOffset

可以把动画时间想象成一个圆环,从中间一个位置开始执行,到结尾再循环执行到刚刚开始的地方。

  • autoreverses

为True时,动画再反向执行一遍。

  • fillMode

动画开始之前或者结束之后的填充行为,默认是kCAFillModeRemoved。前面用到的kCAFillModeForwards是动画结束之后保持最后状态,kCAFillModeBackwards是动画开始之前就保持最开始的状态。

下图可以清晰地看出各个参数的含义,动画演示的是从橘色变成蓝色的过程,横向带变动画时刻。

CAMediaTiming 参数行为

特别指出,这里的speed为0代表动画暂停,与timeOffset一起可以暂停/恢复 动画。

- (void)pauseAnimation:(CALayer *)layer {
    CFTimeInterval pauseTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0;
    layer.timeOffset = pauseTime;
}

- (void)resumeAnimation:(CALayer *)layer {
    CFTimeInterval pauseTime = [layer timeOffset];
    layer.speed = 1;
    layer.timeOffset = 0;
    layer.beginTime = 0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pauseTime;
    layer.beginTime = timeSincePause;
}
暂停/恢复动画

CAMediaTimingFunction

用于计算起点与终点之间的插值,控制动画的节奏,基本有四种,起变换节奏曲线如下图:

CAMediaTimingFunction

POP

POP是facebook开源独立于CoreAnimation的动画方案。与CoreAnimation的区别主要是:

1.POP 在动画的任意时刻,可以保持Model Layer与 Presentation Layer同步,CoreAnimation做不到。

  1. POP可以应用于任意NSObject对象,CoreAnimation只能应用于CALayer。

基本的POP动画有

  • POPBasicAnimation
    用法类似于CABasicAnimation。
  • POPSpringAnimation
    有弹簧效果,节奏曲线如下图:
POPSpringAnimation节奏曲线

可以用springSpeed,springBounciness等控制弹簧的效果。

  • POPDecayAnimation
    衰减效果,常见于ScrollView滑动时停止的衰减效果。

  • POPCustomAnimation
    自定义动画。

Shimmer

Shimmer也是facebook出品的实现闪动效果的动画,iPhone滑动解锁的效果就可以用这个实现。

shimmerView.contentView = shimmerLabel;
shimmerView.shimmeringOpacity = 0.1;
shimmerView.shimmeringAnimationOpacity = 1.0;
shimmerView.shimmeringBeginFadeDuration = 0.3;
shimmerView.shimmering = YES;
Shimmer动画

其原理也很简单,就是添加contentView 作为subView, 然后创建一个CAGradientLayer 作为contentView.layer的mask。移动gradientLayer就可以有这个效果。

参考文章

ios core animation advanced techniques
obj.io
controlling-animation-timing
POP 介绍与实践
谈谈iOS Animation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • Core Animation Basics Layers Provide the Basis for Drawin...
    杰米阅读 522评论 0 0
  • 转载地址:谈谈iOS Animation 零.前言 这里没有太多的代码细节,只是探索iOS动画的基本概念,以及其抽...
    木夜溯阅读 2,349评论 0 4
  • 今天除夕了,买了好多好吃的,嘿嘿,虽然自己比较胖了吧,但是我不介意啊,先吃完再说! 我觉得自己从小到大都没好好吃过...
    4月的小猴子阅读 167评论 0 0
  • 今天,和往年一样,在零点时给向阳发了一条短信:向阳,我喜欢你。 他很快就回复了我:我也喜欢你哈! 看到这条消息时我...
    冬白love阅读 360评论 13 8