深度剖析Cheetah核心架构:Cheetah、CheetahGroup与CheetahProperty三层设计揭秘
【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/Cheetah
Cheetah 是一款用 Swift 编写的轻量级 iOS 动画库,它不依赖系统的动画 API,而是通过一个简单的 CADisplayLink 运行循环逐帧修改视图属性,从而实现移动、旋转、缩放、颜色渐变等丰富动画效果。本文将带你用通俗的方式看懂它的三层核心架构:Cheetah(动画导演)、CheetahGroup(并行动画组)、CheetahProperty(动画最小单元),帮你在阅读源码时迅速建立心智模型。
为什么一个小型动画库需要"三层"结构?
很多新手写动画时会把所有逻辑塞进一个类里,结果代码越写越乱。Cheetah 只用不到 2000 行代码就把"动画怎么编排、怎么并行、怎么逐帧计算"三件事拆得清清楚楚:
| 层级 | 类名 | 职责 | 所在文件 |
|---|---|---|---|
| 导演层 | Cheetah | 接收链式调用、编排顺序、控制播放 | Cheetah/Cheetah.swift |
| 分组层 | CheetahGroup | 管理同一批并行动画、统一时长与锚点 | Cheetah/CheetahGroup.swift |
| 单元层 | CheetahProperty | 单个属性的插值计算与视图更新 | Cheetah/CheetahProperties.swift |
这种分层就像拍电影:导演决定拍什么顺序,场组负责同一场的几个镜头同时拍摄,演员(属性)各自完成自己的表演。
第一层:Cheetah —— 你的动画"总导演"
一切从UIView.cheetah这个属性开始。它是一个通过关联对象(Associated Object)挂载到每个视图上的 Cheetah 实例,每个视图都能独立持有自己的动画序列,源码见 Cheetah/UIView+CheetahAnimation.swift。
Cheetah 类的设计精髓在于流式 API(Fluent API)。你调用它的任何一个方法——move、scale、rotate、alpha、duration……最后都会返回self,从而可以自由地链式书写:
box.cheetah.move(100, 0).duration(0.5).run()链式调用背后其实只有两条主线:
- 加属性:每个动画方法(如
move)会创建一个具体的 Property 对象,并通过addProperty塞入"当前组"; - 加分组:调用
wait()时会addGroup()开一个新组,之前的属性就归到上一组执行——这正是"串行/并行"编排的秘密。
此外,Cheetah 还管理着播放状态(running、paused、groupIndex、repeatCount),并支持anchorTop、anchorCenter等锚点快捷属性,让你在旋转、缩放时自由指定中心点。真正开始播放时,run()只做一件事:把自身注册进全局的 CheetahManager,然后坐等每一帧的"催促"。
第二层:CheetahGroup —— 让动画"同时发生"的容器
CheetahGroup 非常"薄",它自己不做任何计算,只负责聚合与调度:
- 持有本组的
properties数组、统一的duration、delay和anchorPoint; - 每帧被
proceed(dt)驱动时,先累加已用时间,未到delay就直接返回,等待下一帧; - 到达时间后,遍历组内所有属性:每个属性各自推进并返回一个
CATransform3D变换矩阵,Group 把它们依次拼接后一次性赋给view.layer.transform。
这就是为什么以下三个动作会"同时"发生——它们属于同一个 Group:
view.cheetah .move(100, 0) .rotate(M_PI * 2) .scale(1.5) .run()而位置、大小这类不走变换矩阵的属性,则由各自的 Property 直接写入视图。Group 全部属性完成后返回true,Cheetah 就会把groupIndex前进一格,推进到下一组——串行动画就此实现。
第三层:CheetahProperty —— 动画的最小计算单元
CheetahProperty 是整个架构里"干活最多"的一层,但它的基类(Cheetah/CheetahProperties.swift)设计得极其克制,只定义了一个清晰的三阶段生命周期:
- prepare():动画开始(或循环回来)时,记录当前值
from并清零计时; - calc():结合"绝对/相对"标志算出目标值
toCalc(比如move(100, 0)的终点就是当前位置加 100); - update():每帧根据进度
current插值,把结果写回视图。
进度推进的核心是proceed(dt):把elapsed累加到delay + duration为止,并支持两种节奏——
- 缓动(Easing):通过
Easings提供的 30+ 种缓动函数(正弦、弹性、回弹、贝塞尔曲线等,源码见 Cheetah/Easings.swift)做非线性插值; - 弹簧(Spring):基于张力与摩擦的半隐式欧拉积分,模拟真实物理回弹,源码见 Cheetah/Spring.swift。
在这套通用骨架之下,具体属性被按动画对象分成了四个文件,各管一摊:
- Cheetah/CheetahViewProperties.swift:
frame、size、alpha、backgroundColor等视图级属性; - Cheetah/CheetahLayerProperties.swift:
position、rotation、scale、圆角、边框等图层属性; - Cheetah/CheetahLabelProperties.swift:
textColor等文本属性; - Cheetah/CheetahConstraintProperties.swift:Auto Layout 约束的
constant动画。
由于基类是open的,你还可以继承 CheetahProperty 自定义任意属性的动画——这是该库"想动什么就能动什么"的底气所在。
幕后推手:CheetahManager 的显示循环
三层结构要跑起来,还需要一个"心跳"。Cheetah/CheetahManager.swift 中的 CheetahManager 是全局单例,它用CADisplayLink与屏幕刷新同步:
- 第一个 Cheetah 调用
run()时,管理器启动 DisplayLink; - 每帧计算与上一帧的时间差
dt,依次驱动所有在跑的动画,动画结束后自动清理、解除视图上的关联对象; - 当所有动画都完成后,DisplayLink 自动销毁——零动画时零开销,这对性能敏感的应用非常友好。
同时它提供了全局的pause()/resume()/stop(),方便在页面切换等场景整体暂停动画。
小结:这套三层设计教给我们什么?
🎯职责单一:导演编排、分组调度、单元计算,各自独立,互不越界; 🎯数据驱动:动画不是一步到位的"起点到终点",而是每帧根据dt增量推进,因此天然支持暂停、循环、相对偏移; 🎯可扩展性:新增一种属性只需继承 CheetahProperty 实现三个小方法,主架构一行不用改; 🎯性能自觉:单例管理器 + 按需启停的 CADisplayLink,让小库也有大库级的资源意识。
当你下次想动手写一个 iOS 动画库,或者阅读其他动画框架源码时,不妨对照这三层去拆解:先问"谁在编排顺序",再问"谁负责并行",最后问"谁在算每一帧的数值"——答案往往就浮出水面了。
【免费下载链接】CheetahEasy animation library on iOS with Swift2项目地址: https://gitcode.com/gh_mirrors/chee/Cheetah
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考