前言

自己的语言能力有限,借鉴别人的文章理解并写下这篇文章。

参考链接:iOS CoreAnimation专题——原理篇(三) CALayer的模型层与展示层 - 知乎

使用Swift语言,深入CALayer内部,通过CABasicAnimation动画来探究CALayer的呈现层(presentation)和模型层(model)。

1.首先cmd 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. */open func presentation() -> Self?/* 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. */open func model() -> Self

各位看官自行翻译一下吧。需要特别注意的是呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用presentationLayer将会返回nil.

2.下面我们使用CABasicAnimation添加一个改变view位置的动画,代码如下:
​​​​

    var redLayer = CALayer.init()override func viewDidLoad() {super.viewDidLoad()//redLayerredLayer.backgroundColor = UIColor.red.cgColorredLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)self.view.layer.addSublayer(redLayer)//动画let annimation = CABasicAnimation.init(keyPath: "position")annimation.duration = 2annimation.fromValue = CGPoint(x: 80, y: 80)annimation.toValue = CGPoint(x: 200, y: 500)redLayer.add(annimation, forKey: nil)}

也就是指定了动画的四要素:动什么(keyPath)、动多久(duration)、从什么样子开始(fromValue)、动到什么样子(toValue)。这样CABasicAnimation就能在duration内对keyPath指定的属性从fromValue到toValue之间进行插值。然后我们将动画施加给layer,这样它就会按
照我们的四要素进行动画了。

注意到fromValue和我们初始化redLayer时的position是不一样,初始值position为(80+50,80+500)

我们运行一下将会有如下效果:
​​​​​​​

​​​​​​​​​​​​​​

我们看到动画结束之后 redLayer的位置又回到了原来的位置,原因是什么呢

3. 瞎子和瘸子

网上有一堆的瞎子瘸子比如的文章,感觉解释的很好这里就直接摘抄了。

从前有一个瞎子和一个瘸子,瘸子看得见路,但是不能自己走,瞎子能自己走,但是他看不见路。于是他们想到了一个办法:瞎子背着瘸子,这样瘸子就指挥瞎子如何走路,瞎子负责走路,每走一步,瞎子都会停下来听从瘸子的指挥,这样他们就不紧不慢的走向了目的地。

在CALayer内部也有一个瞎子和一个瘸子:presentation(以下简称P)和model(以下简称M)。presentation负责走路(绘制内容),而model负责看路(如何绘制)。

P有这样的特点:

1.我们看到的一切内容都是p的

2.p只有在下次屏幕刷新的时间才会进行绘制

M的特点:

1.对CALayer的各种绘图属性进行赋值和访问实际上都是访问的M的属性,比如bounds,background,position等

2,对这些属性进行赋值,不会影响p,也就不会影响绘制内容。

那么为什么我们对M的属性进行赋值,“与此同时”视图的显示状态就会发生改变呢?

如果我们用t0表示我们对属性进行赋值的时刻,t1表示下次屏幕刷新的时刻,t0到t1之间的间隔相当短(小于1/60秒),我们的人眼根本察觉不到这样的间隔时间存在,所以我们的感觉就是:赋值和界面绘制是同时进行的。然而实际上,赋值和界面绘制之间有一个t1-t0的时间间隔。

所以总结以上信息,我们可以知道P和M是这样进行交互的:当下次屏幕刷新信号到来时(屏幕重绘时),P为了绘制内容到屏幕上,它会去找M要各种它需要的用来绘制的属性,然后用这些属性的信息来绘制。

直到屏幕刷新信号到来才进行绘制,这种方式能够极大提高绘制效率。考虑这样一种情况,如果连续写了以下三行代码:redLayer.frame = …; redLayer.backgounrdColor = …; redLayer.position = …;,如果每次赋值就会导致屏幕的重绘,这样就会有三次重绘,也就是GPU会执行三次绘制操作。而如果我们先把绘制信息存起来,当需要绘制的时候(屏幕刷新)再用这些信息去重绘,GPU就只会执行一次绘制操作。

上面三行代码,只是对M的属性赋值了而已,没有任何的绘制操作,当下次屏幕刷新信号到来时,P才会去找M要这些属性,进行一次绘制。执行这三次赋值(执行三行代码​​​​​​​)所需的时间远远小于两次屏幕刷新的间隔,所以这样三行代码是一定能够在下次绘制开始前执行完的。也就是说,当我们写了这三行代码后,视图的显示内容不会改变三次,而只会改变一次。

如果我们继续按照瞎子和瘸子的理论进行下去,P背着M,如果我们在下次屏幕刷新前(P走下一步之前)对M进行了三次操作:把M移动到点1,把M移动到点2,把M移动到点3。那么下次屏幕刷新时,P会直接问M“你在哪啊”,M说我在点3呢,你这样这样走过来,于是P就屁颠屁颠的按照M的指挥跑到点3去了,它是不会先跑到点1再跑到点2再跑到点3的。

4.CAAnimation对presentation的控制

P这个瞎子背着M这个瘸子,他们互帮互助,完成各种显示的逻辑。每当屏幕刷新的时候,如果没有其他的信息告诉P它应该如何绘制,那么P就只能去问M,然后回到M的状态。

这样一个平衡会在一个CAAnimation被加到CALayer上后会被打破。当我们调用了layer的addAnimation方法后,一个CAAnimation(以下简称A)就控制了P,就相当于A一脚把M从P身上踹了下来,然后P开始背着A走。

现在P要如何显示就完全由A来控制了,因为A被指定了在duration中从fromValue变到toValue。所以在动画的持续时间内,M被丢在了原地,P则背着A到处跑(毕竟P是个瞎子,根本不知道他脑袋顶上的家伙是谁),每一步该如何走都是A通过插值计算出来的(通过duration、from、to就能计算任意时刻P的状态了)。

运行下面的代码可以看出P背着A跑时,M原地不动:

CADisplayLink每(1/60)s执行一次selector方法。

 var redLayer = CALayer.init()var displayLink : CADisplayLink!override func viewDidLoad() {super.viewDidLoad()//redLayerredLayer.backgroundColor = UIColor.red.cgColorredLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)self.view.layer.addSublayer(redLayer)//动画let annimation = CABasicAnimation.init(keyPath: "position")annimation.duration = 2annimation.fromValue = CGPoint(x: 80, y: 80)annimation.toValue = CGPoint(x: 200, y: 500)redLayer.add(annimation, forKey: nil)displayLink = CADisplayLink.init(target: self, selector: #selector(getFrame))displayLink.add(to: RunLoop.main, forMode: .default)}@objc func getFrame() {print("动画过程中的frame改变")print("modelFrame        ---- \(redLayer.model().frame)")print("presentationFrame ---- \(redLayer.presentation()!.frame)")print("\n")}//结果动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (30.23992655798793, 30.83974295295775, 100.0, 100.0)动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (31.154924668371677, 34.04223633930087, 100.0, 100.0)动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (32.20869496464729, 37.730432376265526, 100.0, 100.0)动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (33.218724578619, 41.26553602516651, 100.0, 100.0)..................动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (147.16021299362183, 440.0607454776764, 100.0, 100.0)动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (148.15154314041138, 443.5304009914398, 100.0, 100.0)动画过程中的frame改变
modelFrame        ---- (80.0, 80.0, 100.0, 100.0)
presentationFrame ---- (149.15820837020874, 447.0537292957306, 100.0, 100.0)

动画结束后,默认情况A就被移除了,这时候P身上啥也没有了。那么在下一次屏幕刷新的时候,没有其他的信息告诉P它应该如何绘制,所以它就又开口了“M你在哪里啊”,M说“我在这里,快过来”。于是P就回到M那里去了,继续由M指挥,所以动画结束后,你会看到视图又回去了。

如果想要P在动画结束后就停在当前状态而不回到M的状态,我们就需要给A设置两个属性,一个是A.isRemovedOnCompletion = fase;表示动画结束后A依然影响着P,另一个是A.fillMode = .forwards,这两行代码将会让A控制住P在动画结束后保持不变(不会回到M的状态),代码是这个样子:

 override func viewDidLoad() {super.viewDidLoad()//redLayerredLayer.backgroundColor = UIColor.red.cgColorredLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)self.view.layer.addSublayer(redLayer)//动画let annimation = CABasicAnimation.init(keyPath: "position")annimation.duration = 2annimation.fromValue = CGPoint(x: 80, y: 80)annimation.toValue = CGPoint(x: 200, y: 500)annimation.isRemovedOnCompletion = falseannimation.fillMode = .forwardsredLayer.add(annimation, forKey: nil)}

运行一下,我们的动画终于停在了终点处,就像这样:

​​​​​​​​​​​​​​

5.模型与显示的同步

我们可以通过设置isRemovedOnCompletion = false以及fillMode来让动画结束后保持状态,但是此时我们的P和M不同步,我们看到的P是toValue的状态,而M则还是自己原来的状态。举个栗子,我们初始化一个layer,它的状态为1,我们给它加个动画,from是0,to是2,设置fillMode为.forewards,则动画结束后P的状态是2,M的状态是1,这可能会导致一些问题出现。比如你点P所在的位置点不动,因为响应点击的是M。所以我们应该让P和M同步。

在CABasicAnimation的文档中写了这样一句话:如果不设置toValue,则CABasicAnimation会从fromValue到M的值之间进行插值。也就是说,如果不设置toValue,则CABasicAnimation会把M的值作为toValue,所以我们就可以在加动画的时候只设置fromValue,再手动修改M的值到你想要动画停止的那个状态就保持同步了。

我们可以扩展到任意的CAAnimation对象,比如CAKeyFrameAnimation,都可以通过设置M的值到动画结束的状态来保持P和M的同步。

所以我们的代码可能就会写成这样:

 override func viewDidLoad() {super.viewDidLoad()//redLayerredLayer.backgroundColor = UIColor.red.cgColorredLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)redLayer.position = CGPoint(x: 200, y: 500)self.view.layer.addSublayer(redLayer)//动画let annimation = CABasicAnimation.init(keyPath: "position")annimation.duration = 2annimation.fromValue = CGPoint(x: 80, y: 80)
//        annimation.toValue = CGPoint(x: 200, y: 500)
//        annimation.isRemovedOnCompletion = false
//        annimation.fillMode = .forwardsredLayer.add(annimation, forKey: nil)
}

当我们写redLayer.position = CGPoint(x: 200, y: 500) 的时候,只是对M赋值,再次强调,它不会影响P的显示,而当P想要显示的时候,它已经被A控制了,所以P会从一开始就在position为(80,80)那里然后动画地移动到(200,500),不会出现在(200,500)那里闪一下的情况。
运行一下,效果就是这样:

6.总结

在CALayer内部,它控制着两个属性:presentation(以下称为P)和model(以下称为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动画都可以通过设置model到动画结束的状态来实现P和M的同步。

Swift CoreAnimation ---- CALayer的呈现层和模型层相关推荐

  1. Django model层 mysql_Django模型层(models.py)之模型创建

    Django数据库操作是十分重要的内容,这两天简单学习了数据库的操作,这里做个总结. 1.ORM简介 详细介绍可以参考这篇博客: 简单的来说,ORM就是对象-关系-映射.它实现了数据模型与数据库的解耦 ...

  2. 第六章节 三层架构(二. 模型层与数据访问层)

    一.模型层 1.由于三层之间存在数据交互,所以需要中间介质那就是"模型层",模型层包括与数据库表相对应的实体类,"实体类就相当于数据库中的数据表,实体类里的属性就相当于数 ...

  3. java中表示层 控制层 业务逻辑层 数据访问层

    控制层(controller):的职能是负责读取视图表现层的数据,控制用户的输入,并调用业务层的方法: 业务层(service):需要根据系统的实际业务需求进行逻辑代码的编写,有些业务逻辑需要通过与数 ...

  4. iOS 动画之CoreAnimation(CALayer)

    CoreAnimation基本介绍 CoreAnimation动画位于iOS框架的Media层 CoreAnimation动画实现需要添加QuartzCore.Framework CoreAnimat ...

  5. CoreAnimation (CALayer 动画)

    CoreAnimation基本介绍: CoreAnimation动画位于iOS框架的Media层 CoreAnimation动画实现需要添加QuartzCore.Framework CoreAnima ...

  6. 【网络基础概念】: 网络相关硬件、TCP/IP四层协议模型、OSI七层协议模型。

    # 时人不识凌云木,直待凌云始道高 # 大家好,我是码辣,是一只正在努力的小小猿一枚,希望大家多多关照 ^v^. # 初次见面,希望大家能够喜欢.(如果你认为文章可以,别忘一键三连呀!嘿嘿. # 有什 ...

  7. 计算机网络7层协议模型,计算机网络(一) OSI七层模型及TCP/IP dubbo协议

    3.TCP/IP 四层模型: TCP/IP协议:是一个网络通信模型,是OSI 七层模型的简化,为4层模型,泛指众多(TCP,UDP,IP等)协议: OSI TCP/IP 功能 协议 应用层 应用层 文 ...

  8. 【文本分类】深入理解embedding层的模型、结构与文本表示

    [1] 名词理解   embedding层:嵌入层,神经网络结构中的一层,由embedding_size个神经元组成,[可调整的模型参数].是input输入层的输出.   词嵌入:也就是word em ...

  9. Paddle网络结构中的层和模型

    简 介: 这是 Paddle中的模型与层 的内容学习笔记.对于Paddle中的层的构造,操作进行了初步的测试与相关的学习. 关键词: Layer,Paddle #mermaid-svg-gE0XomQ ...

最新文章

  1. IDEA 上位?不!Eclipse Theia 1.0 发布!
  2. FreeRTOS 事件标志组 ——提高篇
  3. python以垂直方式输出hello world_python3提问:垂直输出Hello World,全部代码不超过2行....
  4. 把执行结果转成json对象报错_于一次JSON格式错误 之 手把手带你走一波FastJSON将对象转成JSON字符串流程...
  5. wpf之默认窗口模板研究
  6. html中鼠标移走的伪元素,a标签的伪元素的应用——link,hover,visited,active
  7. [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型)...
  8. 大数据开发入门怎么学习?
  9. Gin简单明了的教程---上
  10. Excel中导入Unix格式时间戳小技巧
  11. 立志高远;毕业后计划
  12. 获取tinyMCE编辑器中的内容
  13. 计算机组装要哪些硬件,电脑配件,小编教你电脑组装需要哪些配件
  14. 用python写爬虫(一)初识爬虫
  15. 毫秒转换天时分秒倒计时
  16. acwing算法基础__提高__进阶_课
  17. 信捷plc485通信上位机_【新阁教育】穷学上位机系列——搭建STEP7仿真环境
  18. [Java] 查看占用 CPU 最高的线程
  19. 关于win7开热点的一些小备忘
  20. Excel---条件格式与公式

热门文章

  1. OA项目中遇到的问题
  2. bio 生信博主网站 blog
  3. 软连接文件的创建删除
  4. 用R求矩阵的特征值和特征向量
  5. 数据挖掘:探索性数据分析(EDA)
  6. Win7系统无法验证文件数字签名(0xcoooo428)最佳解决方法
  7. Java模拟Post 提交表单数据
  8. 华为交换机SNMP读取LLDP邻居信息的特殊配置
  9. TCP/IP之蓟辽督师
  10. 结对-爬取大麦网演唱会信息-设计文档