ios动画原理 modelLayer和presentationLayer以及点击交互

我们知道,iOS的动画,和其对应的layer有关。

之前在开发的过程中碰到一个问题,那就是,在一个视图的动画过程中,这个视图view和Layer的frame是怎么变化的?

1 动画过程中frame的变化

为了研究动画过程中,view和Layer的frame变化,做了简单的动画打印测试,效果如下:

由结果可知,在UIView动画的回调中添加打印,只会打印一次,且打印的是最终的view和Layer 的位置,这显然不是想要的结果,所以,在动画的回调block中打印是不能实现的;

1.1 添加KVO监听frame的变化

在动画之前,对视图view和layer分别添加kvo监听其frame的变化,看是否能在动画过程中,视图的frame发生变化,从而打印出对应的frame:

如上图打印结果,添加kvo监听,对应的view和layer,只监听到了修改view的一次frame的变化,添加断点,监听的回调也只走一次;

??问题:
1.view的frame变化为什么只有一次?动画的过程中,view的frame不变?
2.为什么监听不到layer 的frame的变化? 最终通过第一次的实验,layer的frame是发生了变化的啊?

1.2 添加CADisplayLink 打印frame的变化

带着1.1中的两个问题,回想到view动画的实现原理,动画的过程其实是一帧一帧显示的,所以,每一帧对应的view或者layer的位置是不断变化的;

所以应该尝试打印在每一帧的显示时,view和layer的frame;

而这恰好可以使用系统自带的CADisplayLink,它的回调频率,和设备的刷帧是一致的,添加测试如下:

注意:添加一个dispatch_after,是为了避免一直打印,不方便查看打印结果,没有其他作用;

打印发现,view和layer的frame还是每次打印的最终位置的frame;

由此,证明了动画过程

view的frame只改变的一次,直接改到了最终的frame

而动画的view的layer,确实是和动画相关的,难道它的frame也只改变了一次?

2 关于modelLayer和presentationLayer

打开CALayer的头文件,发现了其下两个属性:


/* Returns a copy of the layer containing all properties as they were* at the start of the current transaction, with any active animations* applied. This gives a close approximation to the version of the layer* that is currently displayed. Returns nil if the layer has not yet* been committed.** The effect of attempting to modify the returned layer in any way is* undefined.** The `sublayers', `mask' and `superlayer' properties of the returned* layer return the presentation versions of these properties. This* carries through to read-only layer methods. E.g., calling -hitTest:* on the result of the -presentationLayer will query the presentation* values of the layer tree. *// *在-presentationLayer方法的结果上调用时,返回*具有当前模型值的基础层。当调用*非表示层,返回接收者。通话结果*产生展示的交易后的此方法*图层已完成是不确定的。 * // *返回包含所有属性的图层的副本*在当前交易开始时,带有任何活动的动画*适用。这非常接近该图层的版本*当前显示。如果图层尚未返回nil*已承诺。**尝试以任何方式修改返回的图层的效果是*未定义。**返回的`sublayers',`mask'和`superlayer'属性*层返回这些属性的表示形式。这个*进行只读层方法。例如,调用-hitTest:* -presentationLayer的结果将查询演示文稿*层树的值。 * /- (nullable instancetype)presentationLayer;/* When called on the result of the -presentationLayer method, returns* the underlying layer with the current model values. When called on a* non-presentation layer, returns the receiver. The result of calling* this method after the transaction that produced the presentation* layer has completed is undefined. *// *在-presentationLayer方法的结果上调用时,返回*具有当前模型值的基础层。当调用*非表示层,返回接收者。通话结果*产生展示的交易后的此方法*图层已完成是不确定的。 * /- (instancetype)modelLayer;

将回调中的layer,换成了presentationLayer,打印结果如下:

打印发现了一开始想要的结果,原来直接打印view的frame之变化一次是对的,真正的发生动画的是一个叫presentationLayer的东西;

动画的整个过程其实经历了三个树状结构,才显示到了屏幕上:模型树–>呈现树–>渲染树,如图:

通常,我们操作的是模型树;

在重绘周期最后,我们会将模型树相关内容(层次结构、图层属性和动画)序列化,通过IPC传递给专门负责屏幕渲染的渲染进程。渲染进程拿到数据并反序列化出树状结构–呈现树。

这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值

我们可以通过CALayer的presentationLayer方法来访问对应的呈现树图层。

注意:呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用-presentationLayer将会返回nil。

–modelLayer方法: 在呈现图层上调用–modelLayer将会返回它正在呈现所依赖的CALayer。通常在一个图层上调用-modelLayer会返回–self(实际上我们已经创建的原始图层就是一种数据模型)。

一个移动的图层是如何通过数据模型呈现的:

大多数情况下,你不需要直接访问呈现图层,你可以通过和模型图层的交互,来让Core Animation更新显示。

两种情况下presentationLayer呈现图层会变得很有用

  • 同步动画,
  • 动画过程中处理用户交互。

当模型树上带有动画特征时,提交到渲染进程后,渲染进程会根据动画特征,不断修改呈现树上的图层属性,并同时不断的在屏幕上渲染出来,这样我们就看到了动画。

2.1 模型树与呈现树关系的比喻

在CALayer内部,它控制着两个属性:presentationLayer(以下称为P)和modelLayer(以下称为M)。

P只负责显示,M只负责数据的存储和获取

我们对layer的各种属性赋值比如frame,实际上是直接对M的属性赋值;

而P将在每一次屏幕刷新的时候回到M的状态。

比如此时M的状态是1,P的状态也是1,然后我们把M的状态改为2,那么此时P还没有过去,也就是我们看到的状态P还是1,在下一次屏幕刷新的时候P才变为2。而我们几乎感知不到两次屏幕刷新之间的间隙,所以感觉就是我们一对M赋值,P就过去了。

P就像是瞎子,M就像是瘸子,瞎子背着瘸子,瞎子每走一步(也就是每次屏幕刷新的时候)都要去问瘸子应该怎样走(这里的走路就是绘制内容到屏幕上),瘸子没法走,只能指挥瞎子背着自己走。

重点: 动画完成回到原地

可以简单的理解为:一般情况下,任意时刻P都会回到M的状态。

而当一个CAAnimation(以下称为A)加到了layer上面后,A就把M从P身上挤下去了。
现在P背着的是A,P同样在每次屏幕刷新的时候去问他背着的那个家伙,A就指挥它从fromValue到toValue来改变值。而动画结束后,A会自动被移除,这时P没有了指挥,就只能大喊“M你在哪”,M说我还在原地没动呢,于是P就顺声回到M的位置了。

这就是为什么动画结束后我们看到这个视图又回到了原来的位置,是因为我们看到在移动的是P,而指挥它移动的是A,M永远停在原来的位置没有动,动画结束后A被移除,P就回到了M的怀里。

动画结束后,P会回到M的状态(当然这是有前提的,因为动画已经被移除了,我们可以设置fillMode来继续影响P),但是这通常都不是我们动画想要的效果。我们通常想要的是,动画结束后,视图就停在结束的地方,并且此时我去访问该视图的属性(也就是M的属性),也应该就是当前看到的那个样子。按照官方文档的描述,我们的CAAnimation动画都可以通过设置modelLayer到动画结束的状态来实现P和M的同步。

2.2 动画的实现方式

在iOS中,实现动画的方式主要分两大类:

  • CoreAnimation动画
  • 非CoreAnimation动画。

CoreAnimation动画

CoreAnimation动画,即基于事务的动画,是最常见的动画实现方式。动画执行者是专门负责渲染的渲染进程,操作的是呈现树 presentationLayer。我们应该尽量使用CoreAnimation来控制动画,因为CoreAnimation是充分优化过的:

1、更高效的绘制

基于Layer的绘图过程中,CoreAnimation通过硬件操作位图(变换、组合等),产生动画的速度比软件操作的方式快很多。

基于View的绘图过程中,view被改动时会触发的drawRect:方法来重新绘制位图,但是这种方式需要CPU在主线程执行,比较耗时。而CoreAnimation则尽可能的操作硬件中已缓存的位图,来实现相同的效果,从而减少了资源损耗。

2、更高效的动画

在动画过程中,CoreAnimation会通过硬件来一帧一帧的绘制。你所做的就是指定动画的起点和终点,其他的都让CoreAnimation来做。当然你也可以自定义动画参数,否则CoreAnimation会使用合适的默认值。

非CoreAnimation动画

非CoreAnimation动画执行者是当前进程,操作的是模型树 modelLayer。常见的有定时器动画和手势动画。定时器动画是在定时周期触发时修改模型树的图层属性;手势动画是手势事件(比如UIScrollView的didScrollView)触发时修改模型树的图层属性。两者都能达到视图随着时间不断变化的效果,即实现了动画。

非CoreAnimation动画动画过程中实际上不断改动的是模型树,而呈现树仅仅成了模型树的复制品,状态与模型树保持一致。整个过程中,主要是CPU在主线程不断调整图层属性、布局计算、提交数据,没有充分利用到CoreAnimation强大的动画控制功能。

以上部分关于layer的描述摘自文章链接

3 动画过程中的点击交互处理

如果你想让你做动画的图层响应用户输入

你可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。


- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColor whiteColor];self.testview = [[UIView alloc]initWithFrame:CGRectMake(0, 100, 50, 30)];self.testview.backgroundColor = [UIColor orangeColor];self.testview.userInteractionEnabled = NO;[self.view addSubview:self.testview];[UIView animateWithDuration:3.0 animations:^{self.testview.frame = CGRectMake(300, 100, 50, 30);}];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{UITouch *touch = [touches anyObject];CGPoint point = [touch locationInView:self.view];//方案一
//    if (CGRectContainsPoint(self.testview.layer.presentationLayer.frame, point)) {//        [self clickPresntationLayer];
//    }//方案二if ([self.testview.layer.presentationLayer hitTest:point] != nil) {[self clickPresntationLayer];}
}- (void)clickPresntationLayer{self.testview.backgroundColor = [UIColor colorWithRed:(arc4random()%255/255.0) green:(arc4random()%255/255.0) blue:(arc4random()%255/255.0) alpha:1.0];
}

方案一:直接使用了CGRectContainsPoint(CGrect rect, CGPoint point);方法,该方法返回一个BOOL值,判断point是否在rect内部,刚好可以传染presentationLayer的frame和当前点击的point;

方案二:直接使用了hitTest:(CGPoint)p;方法,该方法返回一个CALayer对象,如果点击的point在其内部,返回一个layer对象;

另外,对于CALayer的另一个方法:

  • (BOOL)containsPoint:(CGPoint)p;

做了尝试发现不管用,梳理一下,当前的point在self.view上,而这个方法的point是在layer内部的,所以可能不适用。

之前面试时有碰到问题,在回到UIView和CALayer的区别时:

有回答CALayer不能响应点击事件:但现在来看,通过hitTest方法,确实是可以响应点击事件的。

原文链接

ios动画原理 modelLayer和presentationLayer以及点击交互相关推荐

  1. iOS 动画原理与实现--帧动画、逐帧动画、CALayer

    这篇文章不会教大家如何实现一个具体的动画效果,我会从动画的本质出发,来说说 iOS 动画的原理与实现方式. 什么是动画 动画,顾名思义,就是能"动"的画. 人的眼睛对图像有短暂的记 ...

  2. 解析 iOS 动画原理与实现

    什么是动画 动画,顾名思义,就是能"动"的画. 人的眼睛对图像有短暂的记忆效应,所以当眼睛看到多张图片连续快速的切换时,就会被认为是一段连续播放的动画了. 比如,中国古代的&quo ...

  3. iOS CALayer动画原理分析

    一.引出问题 在开始分析原理之前,我们先来看一个问题: 我们都知道 UIView与 CALayer之间的关系,通俗的来说,UIView内部封装了一个 CALayer, 其中 CALayer负责展示UI ...

  4. IOS之 基本动画原理

    IOS动画分为属性动画和过渡动画.ios4.0之前 属性动画 内容和设置主要放在方括号中既:如下 [UIView beginAnimations:@"move" context:@ ...

  5. 动画原理与实现 浅析

    转载自:http://www.jianshu.com/p/13c231b76594 文/胖花花(简书作者) 原文链接:http://www.jianshu.com/p/13c231b76594 著作权 ...

  6. CALayer与iOS动画 讲解及使用

    iOS CALayer与iOS动画 讲解及使用 关于CoreAnimation 初识CALayer CALayer CAAnimation CAMediaTiming UIView与CALayer动画 ...

  7. iOS动画系列之八:使用CAShapeLayer绘画动态流量图

    这篇文章通过使用CAShapeLayer和UIBezierPath来画出一个动态显示剩余流量的小动画. 最终实现的效果如下: Paste_Image.png 动态效果图: shapeLayerAni. ...

  8. iOS动画系列之九:实现点赞的动画及播放起伏指示器

    iOS动画系列,共十篇.现在写到第九篇啦.感兴趣的可以通过下面的传输门进到其他几篇文章里面. 第一篇:iOS动画系列之一:通过实战学习CALayer和透视的原理.做一个带时分秒指针的时钟动画(上) 第 ...

  9. iOS动画系列之五:基础动画之缩放篇旋转篇Swift+OC

    这一篇主要介绍基础动画之缩放和旋转.这些基本操作分享完之后,我想想可以找个稍微复杂一点点的动画做做啦. 这篇继续基础篇,分享一下缩放和旋转.因为整体思路和平移基本上没有变化,加上源代码里面也有OC版本 ...

最新文章

  1. Vue Router webpack
  2. 按群计数10以内_【乐玩乐学】有趣的计数活动
  3. 基于python的图片修复程序-可用于水印去除
  4. c语言改变cmd 字体大小_嵌入式开发中常见3个的C语言技巧
  5. SVG 教程 (三)圆形,椭圆,直线
  6. mysql定时异地备份_MYsql 异地备份脚本
  7. 转 .net里如何判断中文字符长度
  8. 虚电路网络和数据报网络
  9. mongodb查询find(
  10. grafana快速搭建数据平台
  11. 记车架号识别程序部署参考文档
  12. 小白科普:10Mb独享服务器相当于多少流量?一个月3500GB流量的服务器可以支持多少PV?多少IP访问?
  13. radio默认选中并显示相应信息 php,php selectradio和checkbox默认选择的实现方法详解...
  14. ubuntu安装pinta(图片编辑器)
  15. 支付宝怎么预约新冠疫苗? 疫苗网上预约查询的方法
  16. python使用selenium启动谷歌浏览器无痕模式代码
  17. python网络爬虫 百度网盘_百度网盘爬虫(如何爬取百度网盘)
  18. python中stacked_Python:如何在stacked mod中生成可重复的结果
  19. 【总结】教你怎么将centos7打造成桌面系统
  20. 【20210402期AI简报】TensorFlow-YOLOv3 从本地训练到服务器部署全过程

热门文章

  1. SQLServer连接服务器维护,SQLServer远程连接服务器详细配置(sp_addlinkedserver)
  2. 揭秘 | 一分钟看懂半导体FOWLP封装技术全过程!
  3. ECharts 仪表盘(gauge) 改环形跑道 使用方法分享
  4. Android 根据目标宽度,将bitmap等比缩放。
  5. 山寨不了你,就要使出混身解数置你于死地,某多只是马前卒
  6. WS-Notification的使用---来自阿红
  7. 鑫锘计算机软件公司欺骗员工,我们已经24小时没睡觉-(1)
  8. C++Primer Plus(第六版)第七章编程练习
  9. java松鼠大战代码_fc松鼠大战金手指代码 松鼠大战2金手指代码
  10. 初探win10 x64 SSDT(驱动学习笔记五)