You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用CAKeyframeAnimation实现线性平移与放大组合动画?

问题:实现矩形移动到屏幕中心+放大3倍的同步线性动画

需求:将矩形缓慢移动到屏幕中心,同时将其尺寸放大至原来的3倍。当前代码无法同步两个动画,且动画结束后的位置不正确。

错误代码

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        presentAnimation()
    }

    private lazy var rectView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.layer.borderColor = UIColor.blue.cgColor
        return view
    }()
}

extension ViewController {
    private func setupUI() {
        
        view.addSubview(rectView)
        rectView.snp.makeConstraints { 
            $0.left.equalToSuperview().inset(40)
            $0.top.equalToSuperview().inset(100)
            $0.width.equalTo(40)
            $0.height.equalTo(40)
        }
    }
    
    private func presentAnimation() {
        let testEnlarge = CABasicAnimation(keyPath: "transform.scale")
        testEnlarge.fromValue = 1
        testEnlarge.toValue = 3
        testEnlarge.beginTime = CACurrentMediaTime() + 2
        testEnlarge.duration = 1
        testEnlarge.fillMode = .forwards
        testEnlarge.isRemovedOnCompletion = false
        
        let positionX = view.center.x - ((rectView.frame.width * 3)/2)
        let positionY = view.center.y - ((rectView.frame.height * 3)/2)
        
        let testTranslation = CAKeyframeAnimation(keyPath: "transform.translation")
        testTranslation.values = [CGPoint(x: positionX, y: positionY)]
        testTranslation.beginTime = CACurrentMediaTime() + 2
        testEnlarge.duration = 1
        testEnlarge.fillMode = .forwards
        testTranslation.isRemovedOnCompletion = false
        
        CATransaction.begin()
        rectView.layer.add(testEnlarge, forKey: nil)
        rectView.layer.add(testTranslation, forKey: nil)
        CATransaction.commit()
    }
}

错误效果演示

错误动画演示


解决方案

方案一:使用UIView动画(推荐)

UIView动画API更适合组合动画场景,自动同步多属性动画,且无需手动处理图层状态,代码更简洁:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        presentAnimation()
    }

    private lazy var rectView: UIView = {
        let view = UIView()
        view.backgroundColor = .yellow
        view.layer.borderColor = UIColor.blue.cgColor
        view.layer.borderWidth = 1 // 补充边框宽度,否则无法显示蓝色边框
        return view
    }()
}

extension ViewController {
    private func setupUI() {
        view.addSubview(rectView)
        rectView.snp.makeConstraints { 
            $0.left.equalToSuperview().inset(40)
            $0.top.equalToSuperview().inset(100)
            $0.width.equalTo(40)
            $0.height.equalTo(40)
        }
    }
    
    private func presentAnimation() {
        // 延迟2秒启动动画
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            UIView.animate(withDuration: 1, animations: {
                // 1. 放大至原尺寸的3倍
                self.rectView.transform = CGAffineTransform(scaleX: 3, y: 3)
                // 2. 移动到屏幕中心
                self.rectView.center = self.view.center
            })
            // UIView动画默认会修改视图真实属性,动画结束后状态自动保留
        }
    }
}

方案二:修复Core Animation代码

若必须使用Core Animation,需修正位移计算逻辑和动画参数:

private func presentAnimation() {
    let testEnlarge = CABasicAnimation(keyPath: "transform.scale")
    testEnlarge.fromValue = 1
    testEnlarge.toValue = 3
    testEnlarge.beginTime = CACurrentMediaTime() + 2
    testEnlarge.duration = 1
    testEnlarge.fillMode = .forwards
    testEnlarge.isRemovedOnCompletion = false
    
    // 计算位移偏移量:目标中心 - 原始中心(transform.translation是相对原始位置的偏移)
    let originalCenter = rectView.center
    let targetCenter = view.center
    let offsetX = targetCenter.x - originalCenter.x
    let offsetY = targetCenter.y - originalCenter.y
    
    let testTranslation = CABasicAnimation(keyPath: "transform.translation")
    testTranslation.fromValue = CGPoint(x: 0, y: 0)
    testTranslation.toValue = CGPoint(x: offsetX, y: offsetY)
    testTranslation.beginTime = CACurrentMediaTime() + 2
    testTranslation.duration = 1 // 给位移动画设置正确时长,保证同步
    testTranslation.fillMode = .forwards
    testTranslation.isRemovedOnCompletion = false
    
    CATransaction.begin()
    rectView.layer.add(testEnlarge, forKey: "scale")
    rectView.layer.add(testTranslation, forKey: "translation")
    CATransaction.commit()
    
    // 动画结束后更新视图真实属性,避免图层回退到原始状态
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        self.rectView.transform = CGAffineTransform(scaleX: 3, y: 3).translatedBy(x: offsetX, y: offsetY)
    }
}

关键说明

  • UIView动画直接修改视图的transform和center属性,动画结束后状态自动保留,无需额外处理。
  • Core Animation作用于图层,默认动画结束后图层会回到原始状态,因此需要设置fillMode = .forwards和isRemovedOnCompletion = false,同时建议在动画结束后更新视图真实属性,避免后续操作导致状态异常。
  • 位移动画的正确逻辑是计算目标位置与原始位置的偏移量,而非直接设置绝对坐标,因为transform.translation表示的是相对于原始位置的偏移。

内容的提问来源于stack exchange,提问作者malcopolocei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 05:50:28