原理念经,夯实基础: 从圆角动画,探索 CATransaction 的作用

1,342 阅读2分钟

使用 CATransaction 同步 UIKIt 动画和 CoreAnimation 动画

UIKIt 动画,背后是 CoreAnimation 动画

同时使用 UIKIt 动画和 CoreAnimation 动画,是个好想法

下面的例子,使用 UIKIt 动画改 frame 尺寸大小,使用 CoreAnimation 来一个圆角动画

customKeyFrames.gif


class ViewController: UIViewController {
    
    var styledButton = { () -> UIButton in
        let edge: CGFloat = 125
        let btn = UIButton(frame: CGRect(x: 0, y: 0, width: edge, height: edge))
        btn.layer.cornerRadius = edge/2
        btn.backgroundColor = UIColor(red: 57/255.0, green: 73/255.0, blue: 171/255.0, alpha: 1)
        return btn
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        styledButton.center = view.center
        view.addSubview(styledButton)
    }
    

    @IBAction func start(_ sender: UIButton) {
        // Call animation
        animateButton()
    }
    
    fileprivate func animateButton(duration: CFTimeInterval = 1.0) {
       let oldValue = styledButton.frame.width/2
       let newButtonWidth: CGFloat = styledButton.frame.width/5
       
       //  通过 CAMediaTimingFunction,设置一个弹簧动画 
       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()
    }
}



原理念经部分:

UIView 有一个 layer 属性 CALayer,CALayer 负责动画。

Core Animation 有三颗树,Layer Tree 层次树 ,Model Tree 模型树,Render Tree 渲染树

  • 层次树,一般 addSubViewremoveFromSuperView
  • 模型树,通过 layer.model()访问。

视图属性的改变,直接改模型树,直接跳过去了,没动画。

  • 渲染树,通过 layer.presentation() 访问。

动画过程中,视图属性的改变,改渲染树,有动画。

动画的一般操作,让模型树与渲染树,保持同步

视图的实际状态,就是动画开始状态

举个例子:

动画开始前,view.frame 等,就是视图的实际状态

CoreAnimation 的 fromValue ,就是动画的开始状态

Core Animation 开发遇到的两个状态

  • 视图的给定状态 (frame 、alpha、color...)( 代码指定的 )
  • 视图的运行状态 / 实际状态

就像代码控制一个视图从 a 点移动 到 b 点,

a 点和 b 点, 给定状态

视图运行在 a 点和 b 点 中间,实际状态

动画出了问题,就刷新状态

CATransaction.flush()


CATransaction 有一个协调的作用

CATransaction 可以把多个动画关联的行为组装好。 确保属性修改预期的动画,都同时提交到了 Core Animation.

同时使用 UIKit 动画和 CoreAnimation 动画,

CATransaction 作用域的动画,都会使用 CATransaction 的配置

举个例子:

因为 CATransaction 配置了动画时长, CATransaction.setAnimationDuration(duration),

CoreAnimation 就不需要指定 duration

没有 CATransaction 统一整合的效果:

圆角动画,和 frame 尺寸缩小动画,不同步

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

       // 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.duration = duration
       cornerAnimation.fromValue = oldValue
       cornerAnimation.toValue = newButtonWidth/2
       
       styledButton.layer.cornerRadius = newButtonWidth/2
       styledButton.layer.add(cornerAnimation, forKey: #keyPath(CALayer.cornerRadius))
       
    }

UIKit 框架非常友好, 直接改, 框架自动处理好了 View 属性动画和 Layer 属性动画的同步

easeInOut.gif

fileprivate func animateButton(duration: CFTimeInterval = 1.0) {
       let newButtonWidth: CGFloat = styledButton.frame.width/5

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

github 链接