iOS動畫事物(CATransaction)

動畫事物

CATransaction是 Core Animation 中的事務(wù)類,在iOS中的圖層中,圖層的每個(gè)改變都是事務(wù)的一部分,CATransaction可以對多個(gè)layer的屬性同時(shí)進(jìn)行修改,同時(shí)負(fù)責(zé)批量的把多個(gè)圖層樹的修改作為一個(gè)原子更新到渲染樹。

屬性和方法了解

  • 創(chuàng)建和提交事物(Creating and Committing Transactions)
 // 當(dāng)前線程創(chuàng)建一個(gè)新的事物(Transaction),可嵌套
 open class func begin()

 // 提交當(dāng)前事物中的所有改動,如果事物不存在將會出現(xiàn)異常
 open class func commit()

 // 提交任意的隱式動畫,將被延遲一直到嵌套的顯示事物被完成
 open class func flush()
  • 重寫動畫時(shí)間(Overriding Animation Duration and Timing)
 // 獲取動畫時(shí)間,默認(rèn)0.25秒
 open class func animationDuration() -> CFTimeInterval
 // 設(shè)置動畫時(shí)間
 open class func setAnimationDuration(_ dur: CFTimeInterval)

 // 默認(rèn)nil,設(shè)置和獲取CAMediaTimingFunction(速度控制函數(shù))
 open class func animationTimingFunction() -> CAMediaTimingFunction?
 open class func setAnimationTimingFunction(_ function: CAMediaTimingFunction?)
  • 失效屬性動畫(Temporarily Disabling Property Animations)
 // 每一個(gè)線程事物屬性都有存取器,即設(shè)置和獲取方法,默認(rèn)為false,允許隱式動畫
 open class func disableActions() -> Bool
 open class func setDisableActions(_ flag: Bool)
  • 回調(diào)閉包(Getting and Setting Completion Block Objects)
 // 動畫完成之后被調(diào)用
 open class func completionBlock() -> (() -> Void)?
 open class func setCompletionBlock(_ block: (() -> Void)?)
  • 管理并發(fā)(Managing Concurrency)
 // 兩個(gè)方法用于動畫事物的加鎖與解鎖 在多線程動畫中,保證修改屬性的安全
 open class func lock()
 open class func unlock()
  • 設(shè)置和獲取事物屬性(Getting and Setting Transaction Properties)
 open class func value(forKey key: String) -> Any?
 open class func setValue(_ anObject: Any?, forKey key: String)

支持的屬性

// 設(shè)置動畫持續(xù)時(shí)間
public let kCATransactionAnimationDuration: String

// 設(shè)置停用animation類動畫
public let kCATransactionDisableActions: String

// 設(shè)置動畫時(shí)序效果
public let kCATransactionAnimationTimingFunction: String

// 設(shè)置動畫完成后的回調(diào)
public let kCATransactionCompletionBlock: String

CATransaction事務(wù)類分為隱式事務(wù)和顯式事務(wù),注意以下兩組概念的區(qū)分:

  • 隱式動畫和隱式事務(wù)

隱式動畫通過隱式事務(wù)實(shí)現(xiàn)動畫 。

  • 顯式動畫和顯式事務(wù)

顯式動畫有多種實(shí)現(xiàn)方式,顯式事務(wù)是一種實(shí)現(xiàn)顯式動畫的方式。

隱式事務(wù)

隱式事務(wù)是基于CALayer,任何對于CALayer屬性的修改,都是隱式事務(wù).這樣的事務(wù)會在run-loop中被提交。

首先看一個(gè)簡單例子

class ViewController: UIViewController {

    lazy var layer: CALayer = {
        let layer = CALayer()
        layer.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
        layer.backgroundColor = UIColor.red.cgColor
        return layer
    }()

    lazy var button: UIButton = {
        let button = UIButton(type: .custom)
        button.setTitle("button", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        button.frame = CGRect(x: 100, y: 250 , width: 100, height: 50)
        button.addTarget(self, action: #selector(buttonClick) , for: .touchUpInside)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.layer.addSublayer(layer)
        view.addSubview(button)
    }

    @objc func buttonClick() {
        layer.backgroundColor = UIColor.yellow.cgColor
    }
}

代碼很簡單,而且并沒有添加動畫相關(guān)的代碼,但運(yùn)行程序之后,單擊按鈕,layer的顏色是有漸變的效果的,即動畫效果,這就是隱式動畫

2018-11-27 14-45-16.2018-11-27 14_45_29.gif

隱式事務(wù)是CoreAnimation的一部分,是對layer-tree進(jìn)行原子更新為render-tree的機(jī)制,由CoreAnimation幫助創(chuàng)建事務(wù),當(dāng)前線程的runloop下次循環(huán)就會自動commit,如果當(dāng)前線程沒有runloop,或者runloop被阻塞,則應(yīng)該使用顯示事務(wù),即手動創(chuàng)建CATransaction

顯示事務(wù)

顯示的調(diào)用事物,修改buttonClick方法

@objc func buttonClick() {
     CATransaction.begin()
     layer.backgroundColor = UIColor.yellow.cgColor
     CATransaction.commit()
}

僅僅是簡單的調(diào)用CATransaction的begin和commit方法就可以實(shí)現(xiàn)顯示事務(wù),運(yùn)行程序之后看到的效果是一樣的。還有相關(guān)方法可以設(shè)置,比如:設(shè)置動畫時(shí)間以及事務(wù)完成的閉包

 @objc func buttonClick() {
     CATransaction.begin()
     CATransaction.setAnimationDuration(2) // 時(shí)間2秒
     CATransaction.setCompletionBlock {
         print("tranction end")
     }
     layer.backgroundColor = UIColor.yellow.cgColor
     CATransaction.commit()
 }

使用非常簡單,但是需要注意代碼的先后順序,需要將更改的關(guān)鍵代碼放在設(shè)置時(shí)間和完成回調(diào)之后,這樣才能達(dá)到想要的效果,如果放在之前,相當(dāng)于在執(zhí)行動畫操作時(shí),還沒有對其進(jìn)行動畫時(shí)間和完成回調(diào)的賦值

在完成的回調(diào)中也可以對layer進(jìn)行修改,同樣默認(rèn)是隱式動畫,如果需要設(shè)置時(shí)間,需要顯示設(shè)置。

 @objc func buttonClick() {
     CATransaction.begin()
     CATransaction.setAnimationDuration(2)
     CATransaction.setCompletionBlock {
     CATransaction.setAnimationDuration(1)
         self.layer.backgroundColor = UIColor.cyan.cgColor
     }
     layer.backgroundColor = UIColor.yellow.cgColor
     CATransaction.commit()
 }

嵌套多個(gè)事務(wù)組

效果類似于一組動畫同時(shí)進(jìn)行的效果的效果

 @objc func buttonClick() {
     CATransaction.begin()
     CATransaction.setAnimationDuration(2)
     CATransaction.setCompletionBlock {
         CATransaction.setAnimationDuration(1)
         self.layer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
     }
     layer.backgroundColor = UIColor.yellow.cgColor

    CATransaction.begin()
    layer.cornerRadius = 20
    CATransaction.commit()
        
    CATransaction.commit()
 }
2018-11-27 15-32-28.2018-11-27 15_32_42.gif

相當(dāng)于先改變顏色和圓角,是同時(shí)進(jìn)行的,修改圓角屬性默認(rèn)是0.25s,最后移動layer。

除了同時(shí)修改layer的動畫,我們還可以組合UIView和CALayer的動畫

 func addStyledButton() {
     styledButton = UIButton(frame: CGRect(x: 0, y: 0, width: 125, height: 125))
     styledButton.backgroundColor = UIColor(red: 57/255.0, green: 73/255.0, blue: 171/255.0, alpha: 1)
     styledButton.layer.cornerRadius = styledButton.frame.width/2
     styledButton.center = self.view.center

     self.view.addSubview(styledButton)
 }

 func animateButton(duration: CFTimeInterval = 1.0) {
     let oldValue = styledButton.frame.width/2
     let newButtonWidth: CGFloat = 60

     let timingFunction = CAMediaTimingFunction(controlPoints: 0.65, -0.55, 0.27, 1.55)

     /* Do Animations */
     CATransaction.begin()
     CATransaction.setAnimationDuration(duration)
     CATransaction.setAnimationTimingFunction(timingFunction)

     // View animations
     UIView.animate(withDuration: duration) {
          self.styledButton.frame = CGRect(x: 0, y: 0, width: newButtonWidth, height: newButtonWidth)
          self.styledButton.center = self.view.center
     }

     // Layer animations
     let cornerAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.cornerRadius))
     cornerAnimation.fromValue = oldValue
     cornerAnimation.toValue = newButtonWidth/2

     styledButton.layer.cornerRadius = newButtonWidth/2
     styledButton.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius))

     CATransaction.commit()
 }
2018-11-27 15-45-58.2018-11-27 15_46_11.gif

如果上面的例子不是CALyer,而是UIView,則并不會觸發(fā)隱式事務(wù)動畫,同樣對于顯示事務(wù)動畫也不會有作用,這是因?yàn)閁IKit禁止了事務(wù)動畫。

之所以CALyer可以對事務(wù)動畫作出響應(yīng),是因?yàn)镃ALyer的實(shí)例方法

open func action(forKey event: String) -> CAAction?

可以對其進(jìn)行響應(yīng),返回對應(yīng)的action。但對于UIView來說,UIView作為CALyer的代理,則根據(jù)名稱來獲取action,會遵循以下順序

1)如果有代理,則調(diào)用代理方法

 optional public func action(for layer: CALayer, forKey event: String) -> CAAction?

2)如果沒有委托,或者委托沒有實(shí)現(xiàn)action(for layer: CALayer, forKey event: String) 方法,圖層接著檢查包含屬性名稱對應(yīng)行為映射的actions字典
3)檢查layer的style層級中每個(gè)actions字典
4)調(diào)用layer的類方法

 open class func defaultAction(forKey event: String) -> CAAction?

所以當(dāng)需要做事務(wù)動畫時(shí),會按照如上順序獲取對應(yīng)的action,如果獲取到的是nil,則不會對事務(wù)動畫做出響應(yīng),如果返回非nil,則可以做事務(wù)動畫,因此,UIView通過其代理方法func action(for layer: CALayer, forKey event: String)返回nil來禁止事務(wù)動畫。

如果想實(shí)現(xiàn)UIView的隱式動畫,可以自定義UIView,并重寫func action(for layer: CALayer, forKey event: String)方法返回對應(yīng)的action

//  CustomView.swift
class CustomView: UIView {
    override func action(for layer: CALayer, forKey event: String) -> CAAction? {
        return layer.actions?[event]
    }
}

//  ViewController.swift
 lazy var button: UIButton = {
        let button = UIButton(type: .custom)
        button.setTitle("button", for: .normal)
        button.setTitleColor(UIColor.black, for: .normal)
        button.frame = CGRect(x: 100, y: 250 , width: 100, height: 50)
        button.addTarget(self, action: #selector(buttonClick) , for: .touchUpInside)
        return button
    }()

lazy var myView: CustomView = {
        let view = CustomView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        view.backgroundColor = UIColor.red
        return view
    }()

 override func viewDidLoad() {
     super.viewDidLoad()
     view.addSubview(myView)
     view.addSubview(button)
 }

 @objc func buttonClick() {
      myView.backgroundColor = UIColor.yellow
  }

運(yùn)行程序之后,可以看到根之前CALayer是一樣的效果

關(guān)于事務(wù)動畫,還有一個(gè)場景會經(jīng)常用到,即鍵盤監(jiān)聽。在某些情況下,需要對鍵盤彈出進(jìn)行監(jiān)聽,并彈出一個(gè)輸入框

 NotificationCenter.default.addObserver(self,
                                        selector: #selector(keyboardWillShow(_:)),
                                        name: UIResponder.keyboardWillShowNotification ,
                                        object: nil)
 NotificationCenter.default.addObserver(self,
                                        selector: #selector(keyboardWillHide(_:)),
                                        name:  UIResponder.keyboardWillHideNotification,
                                        object: nil)

 @objc func keyboardWillShow(_ notification: Notification) {
 }

@objc func keyboardWillHide(_ notification: Notification) {
 }

可以知道輸入框與鍵盤總是保持相同的速率出現(xiàn)和消失,其實(shí)這是因?yàn)樵阪I盤彈出和消失情況下發(fā)送通知,是在事務(wù)動畫之中執(zhí)行的,如果想要禁用動畫效果,只需要在開始和結(jié)束兩個(gè)方法中禁用即可

@objc func keyboardWillShow(_ notification: Notification) {
        UIView.setAnimationsEnabled(false)
        //...
        UIView.setAnimationsEnabled(true)
}

@objc func keyboardWillHide(_ notification: Notification) {
        UIView.setAnimationsEnabled(false)
        //...
        UIView.setAnimationsEnabled(true)
}

參考

CATransaction
隱式動畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容