如何用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
相关产品推荐
相关产品推荐

