iOS

隐式动画大明白

Posted by Maqy on July 14, 2018

原理(CATransaction)

CATransaction

CATransaction,又称事务,这东西主要作用是:只要在该”区间”内的layer的可用来做动画的属性改变了,就会以一种过渡的形式来展现。

使用方式如下:

CATransaction.begin() // 开始
CATransaction.setAnimationDuration(4) // 设置时间
self.sublayer.backgroundColor = UIColor.blue.cgColor // do something
CATransaction.commit() // 结束

以上就能开启一个动画了。

为什么有隐式动画

其实系统有个runloop,每一次循环会在起始位置begin,结束位置commit,所以内部所有的可用来做动画的属性变化了就都会有动画。 但是一定要保证begin和commit对应,也就是基于栈的操作来对应。

UIView.Animation

那么UIView提供了一套动画的简单写法,但这些操作也只是为了便于使用,而且不至于让使用者造成进栈与出栈不对应的情况发生。

/// 动画开始,对应begin
open class func beginAnimations(_ animationID: String?, context: UnsafeMutableRawPointer?)
/// 动画提交,对应commit
open class func commitAnimations()

/// 又或者下面的动画
UIView.animate(withDuration: 5) {
	/// do something
}
可以转换成
CATransaction.begin() // 开始
CATransaction.setAnimationDuration(5) // 设置时间
block() // do something
CATransaction.commit() // 结束

presentation & model

当一个layer的可表现动画的属性发生变化时,会立刻将layer的属性制成变化后的值,但是中间有个动画还要表现,这个会有一个呈现图层来表现。

/// layer的这个属性平时都是nil的,那么当在动画途中时,这个会存在,且是layer的一个完全copy,不过正在动画的值在不断变化,也就是这个是对应呈现图层的layer
open func presentation() -> Self?

/// 这个属性的值是动画后的,不管你现在是不是在动画过程中
open func model() -> Self

那么,有时候需要点击正在位移途中的layer,比如这个layer从位置1移到了位置2,那么这个时候想要在位移过程中,能够处理点击事件,那么就需要根据presentation()的位置来判断

animation 过程

当layer的属性发生变化时,系统会查找一个可执行的动画。是通过这个func来查找的

class CALayer {
    /* Returns the action object associated with the event named by the
     * string 'event'. The default implementation searches for an action
     * object in the following places:
     *
     * 1. if defined, call the delegate method -actionForLayer:forKey:
     * 2. look in the layer's `actions' dictionary
     * 3. look in any `actions' dictionaries in the `style' hierarchy
     * 4. call +defaultActionForKey: on the layer's class
     *
     * If any of these steps results in a non-nil action object, the
     * following steps are ignored. If the final result is an instance of
     * NSNull, it is converted to `nil'. */
    
    open func action(forKey event: String) -> CAAction?
}
查找第一步

看这个layer的delegate有没有实现,如果有会看看actionForLayer:forKey:有没有实现。如果没有会转入第二步。

actionForLayer:forKey:介绍:

返回nil,转第二步;返回NSNull(),关闭动画;返回CAAction,会执行返回的具体动画

查找第二步
open var actions: [String : CAAction]?

这个字典定义了对一个属性关键字的动画,会在这里变查找。

我们也可以在创建layer后直接设置好这个字典

如果没查找到转入第三步

查找第三步
open var style: [AnyHashable : Any]?

同第二步,没有转四

查找第四步
/** Action methods. **/
open class func defaultAction(forKey event: String) -> CAAction?

如果都找不到就转这个了,系统提供了针对一些可动画的属性的默认动画。会直接提供的。

question

  1. 怎么关闭隐式动画?

    更改属性时,使用CATransaction.setDisableActions(true)即可

    也可以在actionForLayer:forKey:返回NSNull()

  2. 为什么view没有隐式动画?

    actionForLayer:forKey:提供了一个more 的实现,应该是返回NSNull()