IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Swift CoreAnimation ---- CALayer的呈现层和模型层 -> 正文阅读

[移动开发]Swift CoreAnimation ---- CALayer的呈现层和模型层

前言

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

参考链接: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()
        
        //redLayer
        redLayer.backgroundColor = UIColor.red.cgColor
        redLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)
        self.view.layer.addSublayer(redLayer)
        //动画
        let annimation = CABasicAnimation.init(keyPath: "position")
        annimation.duration = 2
        annimation.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()
        
     //redLayer
     redLayer.backgroundColor = UIColor.red.cgColor
     redLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)
     self.view.layer.addSublayer(redLayer)
     //动画
     let annimation = CABasicAnimation.init(keyPath: "position")
     annimation.duration = 2
     annimation.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()
        
        //redLayer
        redLayer.backgroundColor = UIColor.red.cgColor
        redLayer.frame  = CGRect(x: 80, y: 80, width: 100, height: 100)
        self.view.layer.addSublayer(redLayer)
        //动画
        let annimation = CABasicAnimation.init(keyPath: "position")
        annimation.duration = 2
        annimation.fromValue = CGPoint(x: 80, y: 80)
        annimation.toValue = CGPoint(x: 200, y: 500)
        annimation.isRemovedOnCompletion = false
        annimation.fillMode = .forwards
        redLayer.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()
        
           //redLayer
        redLayer.backgroundColor = UIColor.red.cgColor
        redLayer.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 = 2
        annimation.fromValue = CGPoint(x: 80, y: 80)
//        annimation.toValue = CGPoint(x: 200, y: 500)
//        annimation.isRemovedOnCompletion = false
//        annimation.fillMode = .forwards
        redLayer.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的同步。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 13:55:57  更:2021-12-08 13:57:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:32:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码