学习CoreAnimation - 隐式动画篇

3,345 阅读6分钟

隐式动画:改变CALyer图层属性,通过CoreAnimation来控制所执行的动画

系统如何实现隐式动画

1、当CALyer的属性被修改的时候,会调用actionForyKey方法,来传递属性名称

2、通过搜索属性名称,会返回nil或者CAAction协议对应的对象,CALyer通过返回的结果,去和当前或先前的值做动画,这里由CoreAnimation来完成,nil值无动画,非空值对应动画的属性内容

事务

动画并不需要手动打开,相反需要明确的关闭,否则会一直存在。我们在处理动画过程中会遇到,我们并不需要指定明确的动画类型,仅仅通过改变一个属性,让coreAnimation来决定最适合的动画。

事务决定动画的实际执行时间,图层行为决定动画类型。事务通过CATransaction类做管理,并且只能用begin(入栈)、commit(出栈)处理,不可以被初始化创建。

每一个可以用来做动画的图层属性,都会被添加到栈顶的事务,任何一次runloop循环都会将改变的属性集中起来,并以0.25s(默认事务动画时间)的频率执行。当我们设置AnimationDuration,可以控制事务的动画时间,使它变快或者变慢。

如果我们直接修改动画当前事务的时间,会导致同一时间如果有别的动画也会受到影响,因此我们只需要在调整或改变动画时间之前,压入一个新的事务

CATransaction.begin()
复制代码

在属性改变后

CATransaction.commit()
复制代码

UIView中beginAnimations和commitAnimations动画,也是基于内部实现了CATransaction,但是在最新的API中明确表示在iOS13后弃用这两个隐式调用的方法,而更加推荐我们使用animatewithDuration的block方法,在block函数中所有属性的改变都会被事务包含,进而避免了误操作begin和commit的风险。

事务的回调

CATransaction为我们提供了一个回调,也就是说如果我们使用这个完成回调方法,当我们规定的动画完成后会调,你可以在这个代码块内部执行下一步动画的操作。

setCompletionBlock {}
// 生命周期:在上一次事务提交并出栈以后才会执行,执行时没有新的事务,所以使用系统默认的事务(系统默认事务时间0.25s)
复制代码

图层行为(重点)

上面说的一切内容都是基于CALyer下图层的属性改变,而不是UIView中所关联的layer图层,在代码调试的过程中,我尝试使用UIView的layer图层进行属性改变的动画操作,发现在这一过程中,我所期待的隐式动画特性并没有发挥作用

--UIView做了什么?

每个UIView对它关联的图层都扮演了一个委托,并且提供了一个actionForLayer:forKey:实现方法,当UIVIew不在block动画的代码块中执行动画,UIView默认会对图层的行为返回nil,如果执行在block动画块的内部,就返回一个非空值

可以打印一下这段代码

let outside = fivrView.action(for: fivrView.layer, forKey: "backgroundColor")
print("Outside: \(outside.debugDescription)")

UIView.animate(withDuration: 1) {
    let inside = self.fivrView.action(for: self.fivrView.layer, forKey: "backgroundColor")
    print("Inside: \(inside.debugDescription)")
}
        
//or
UIView.beginAnimations(nil, context: nil)
        
let inside = self.fivrView.action(for: self.fivrView.layer, forKey: "backgroundColor")
print("Inside: \(inside.debugDescription)")
        
UIView.commitAnimations()
复制代码

是的,当属性在动画块之外发生改变,UIview通过返回nil来禁止隐式动画,同时这个特性被UIView关闭,当然还可以调用以下代码,控制是否禁用隐式动画

CATransaction.setDisableActions(Bool)
复制代码

--总结

  • UIView关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用UIView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖-actionForLayer:forKey:方法,或者直接创建一个显式动画(具体细节见《iOS核心动画高级技巧》第八章)。
  • 对于单独存在的图层,我们可以通过实现图层的-actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐式动画。

--行为

行为通常是一个被Core Animation隐式调用的显式动画对象。自定义行为就是我们将显示的动画作为CALyer的属性添加。这里我们使用是actions字典可以写更少的代码。

图层的动态呈现(重点理解)

当我们设置CALyer的属性,实际上只是定义图层动画结束后的变化,而不是直接通过更新图层的属性值去改变它的内容,理解这段话主要在于理解,这层机制中变化二字的含义。图层需要在变化中产生动画,而非将属性直接作用图层。

当设置CALayer的属性,实际上是在定义当前事务结束之后图层如何显示的模型。Core Animation扮演了一个控制器的角色,并且负责根据图层行为和事务设置去不断更新视图的这些属性在屏幕上的状态。

如果动画时间超过了屏幕刷新时间,coreAnimation需要对屏幕上的图层进行重新组织,并被记录屏幕上每个图层属性的显示值,又被叫做呈现图层。呈现图层是一个独立的图层,可以通过presentationLayer获取呈现图层,呈现图层实际上是模型图层的复制,但它的属性值代表了在任何指定时刻当前外观效果,可以通过获取它的值来获取屏幕上真正显示的值。

呈现图层会在图层首次提交的时候被创建(在屏幕上第一次显示的时候)多数情况下,我们不需要直接去访问呈现图层。

·引用官方图片·

7.4.jpeg

·引用官方的代码·

##使用hitTest可以用来判断指定图层是否被触摸

colorLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
colorLayer.position = CGPoint(x: self.view.bounds.size.width / 2, y: self.view.bounds.size.height / 2)
        
colorLayer.backgroundColor = UIColor.red.cgColor
        
view.layer.addSublayer(colorLayer)

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for touch in touches {
            let point = touch.location(in: self.view)
            if ((self.colorLayer.presentation()?.hitTest(point)) != nil) {
                let red = arc4random() / UInt32(INT_MAX)
                let green = arc4random() / UInt32(INT_MAX)
                let blue = arc4random() / UInt32(INT_MAX)
                
                self.colorLayer.backgroundColor = UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: 1).cgColor
            }else {
                CATransaction.begin()
                
                CATransaction.setAnimationDuration(4)
                
                self.colorLayer.position = point
                
                CATransaction.commit()
            }
            
        }
    }
复制代码

--归纳

基于上面内容我们可以得出比较清晰的结论

呈现图层的属性值特性适合处理同步动画和手势跟随

使用presentationLayer可以获取它,当我们获取它的时候其实是对原模型图层的拷贝

以呈现图层为参考放在交互层面对于用户而言是精准的

它和模型图层的关系:呈现取决于动画轨迹,模型取决于动画起终点

∗∗∗最后∗∗∗

这一章节理论居多,总结为四个点:1.事务对动画的影响2.隐式动画的成因3.图层的行为4.图层的动态值呈现。在学习的过程中的推敲和探索也能发现趣味性,明白原生API最初的设计思路,对于理解CoreAnimation非常有意义。