4、CAAnimation家族详解:CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup的配合使用
说实话,很多iOS开发者做动画,翻来覆去就只会用个UIView.animate。一旦遇到复杂点的动效,就卡住了。
我刚开始带团队那会儿,有个同事做了一个转场动画,代码写了三百多行,全是UIView嵌套回调。我看完头皮发麻——其实用CAAnimationGroup,三十行就能搞定。
今天咱们就把CAAnimation家族这三个核心成员聊透。你掌握了它们,iOS动画这块基本就通了八成。
4.1 CABasicAnimation:最基础的“从A到B”
CABasicAnimation,说白了就是让某个属性从起始值变到结束值。比如让一个圆从左边移到右边,或者让透明度从1变成0。
我习惯这么写:
let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = 50
animation.toValue = 300
animation.duration = 1.0
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
// 记得设置这个,不然动画结束会跳回原位
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
view.layer.add(animation, forKey: "moveAnimation")
嗯,这里要注意一个坑。很多人第一次用CABasicAnimation,发现动画结束后控件“啪”一下跳回原位。为什么会这样?
因为CAAnimation默认只在渲染层生效,动画结束后模型层的值没变。所以要么设置fillMode和isRemovedOnCompletion,要么在动画结束后手动更新layer的model值。
4.2 CAKeyframeAnimation:不止是起点和终点
如果你想让动画沿着一条曲线走,或者中间经过几个关键点,CABasicAnimation就不够用了。这时候得上CAKeyframeAnimation。
举个例子,让一个球沿着抛物线轨迹运动:
let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 300))
path.addQuadCurve(to: CGPoint(x: 300, y: 300),
controlPoint: CGPoint(x: 175, y: 50))
keyframeAnimation.path = path.cgPath
keyframeAnimation.duration = 2.0
keyframeAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
keyframeAnimation.calculationMode = .paced
ballView.layer.add(keyframeAnimation, forKey: "parabolaAnimation")
你想想看,如果不用CAKeyframeAnimation,你要怎么实现这个抛物线?用CADisplayLink逐帧计算?那代码量就上去了。
4.3 CAAnimationGroup:把动画“打包”起来
单个动画能做的事情有限。实际项目中,我们经常需要同时执行多个动画——比如一个按钮既要放大,又要变色,还要旋转。
用CAAnimationGroup,你可以把多个动画组合成一个组,统一管理:
// 1. 缩放动画
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1.0
scaleAnimation.toValue = 1.5
// 2. 透明度动画
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 1.0
opacityAnimation.toValue = 0.5
// 3. 旋转动画
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0
rotationAnimation.toValue = Double.pi * 2
// 组合起来
let group = CAAnimationGroup()
group.animations = [scaleAnimation, opacityAnimation, rotationAnimation]
group.duration = 2.0
group.repeatCount = .infinity
group.autoreverses = true
group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
view.layer.add(group, forKey: "groupAnimation")
我在项目中遇到过这样一个需求:一个加载动画需要同时旋转和呼吸(缩放+透明度变化)。如果用多个独立的CAAnimation分别添加,很难保证它们同步结束。用CAAnimationGroup就简单了——所有子动画共享duration和timingFunction。
4.4 三者的配合使用:一个实战案例
光讲理论没意思。咱们来个真实场景:做一个“购物车添加商品”的动效。
需求是这样的:点击商品,商品图片飞入购物车,同时购物车图标弹一下。
// 第一步:图片从商品位置飞到购物车(路径动画)
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
let bezierPath = UIBezierPath()
bezierPath.move(to: productCenter)
bezierPath.addCurve(to: cartCenter,
controlPoint1: CGPoint(x: midX, y: productCenter.y),
controlPoint2: CGPoint(x: midX, y: cartCenter.y))
pathAnimation.path = bezierPath.cgPath
pathAnimation.duration = 0.6
// 第二步:图片在飞行过程中逐渐缩小
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1.0
scaleAnimation.toValue = 0.3
scaleAnimation.duration = 0.6
// 第三步:购物车图标弹跳
let bounceAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
bounceAnimation.values = [1.0, 1.4, 0.9, 1.1, 1.0]
bounceAnimation.keyTimes = [0, 0.2, 0.4, 0.6, 1.0]
bounceAnimation.duration = 0.4
bounceAnimation.beginTime = 0.5 // 等图片飞到一半再开始
// 组合飞行动画
let flyGroup = CAAnimationGroup()
flyGroup.animations = [pathAnimation, scaleAnimation]
flyGroup.duration = 0.6
flyGroup.fillMode = .forwards
flyGroup.isRemovedOnCompletion = false
// 分别添加到两个视图
productImageView.layer.add(flyGroup, forKey: "flyToCart")
cartIconView.layer.add(bounceAnimation, forKey: "bounce")
你看,这个动效里三个动画家族成员都用上了:
- CAKeyframeAnimation:控制图片沿贝塞尔曲线飞行
- CABasicAnimation:控制图片缩放
- CAAnimationGroup:把飞行和缩放打包同步执行
4.5 性能优化:别让动画拖垮你的FPS
动画做得好不好,最终看帧率。我见过太多人把动画做出来了,但一跑就掉帧。
几个关键点:
- 尽量操作transform和opacity——这两个属性不会触发离屏渲染,性能最好
- 避免同时操作太多layer——我建议单个动画组里的子动画不要超过5个
- 用shouldRasterize做缓存——如果动画视图有复杂阴影或圆角,开启光栅化能大幅提升性能
- 及时移除不需要的动画——用removeAllAnimations()清理,尤其是cell复用场景
好了,CAAnimation家族这三个成员,你只要理解它们的定位:CABasicAnimation做简单变化,CAKeyframeAnimation做复杂路径,CAAnimationGroup做组合控制。配合使用,基本能覆盖90%的UI动效需求。
下一章咱们聊聊更高级的CASpringAnimation和自定义时序曲线,到时候你会发现,原来“弹性动画”可以这么简单。