浅谈Layer和Animation

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

Layer 与 View

浅谈Layer和Animation_第1张图片
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)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];
}
浅谈Layer和Animation_第2张图片
改变颜色动画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;
}]; 
浅谈Layer和Animation_第3张图片
改变颜色动画 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和Animation_第4张图片
图层树状结构以及对应的视图层级

每一个视图都有一个父视图以及若干个子视图,这形成了一个树状的层级关系。对应地,每个视图的图层也有一个平行的层级关系,称之为图层树(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)的区别。

浅谈Layer和Animation_第5张图片
默认锚点(0.5,0.5),铅笔的中心点在路径上移动
浅谈Layer和Animation_第6张图片
锚点改为(0,1.0),铅笔的笔尖在路径上移动

position

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

浅谈Layer和Animation_第7张图片
anchorPoint与position关系

Animations

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

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

浅谈Layer和Animation_第8张图片
CAAnimation 继承关系

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

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @0;
animation.toValue = @200;
animation.duration = 1;
[circle.layer animation forKey:@"basic"];
浅谈Layer和Animation_第9张图片
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是动画开始之前就保持最开始的状态。

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

浅谈Layer和Animation_第10张图片
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;
}
浅谈Layer和Animation_第11张图片
暂停/恢复动画

CAMediaTimingFunction

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

浅谈Layer和Animation_第12张图片
CAMediaTimingFunction

POP

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

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

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

基本的POP动画有

  • POPBasicAnimation
    用法类似于CABasicAnimation。
  • POPSpringAnimation
    有弹簧效果,节奏曲线如下图:
浅谈Layer和Animation_第13张图片
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;
浅谈Layer和Animation_第14张图片
Shimmer动画

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

参考文章

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

你可能感兴趣的:(浅谈Layer和Animation)