Makepad 的动画系统是一个声明式的动画框架,它通过状态(State)和过渡(Transition)的概念来管理 UI 动画。
让我们通过一个「正在输入」动画组件来深入理解整个系统。
让我们先看 Animator
的核心数据结构:
1pub struct Animator {
2 // 是否忽略找不到的动画状态
3 pub ignore_missing: bool,
4 // 指向 Live DSL 中动画定义的引用
5 pub live_ptr: LiveRef,
6 // 当前动画状态数据
7 pub state: Option<Vec<LiveNode>>,
8 // 下一帧的调度器
9 pub next_frame: NextFrame,
10}
这个结构看起来很简单,但它实际上是一个非常精巧的设计:
live_ptr
- 保存了指向 Live DSL 中动画定义的引用,使得 Animator 可以在需要时查找和加载动画状态定义state
- 存储当前正在执行的动画状态,包括所有正在进行的插值计算next_frame
- 用于调度下一帧动画的渲染这个系统的工作方式是基于状态转换的:
[LiveId; 2]
)animate_to()
: 平滑过渡到新状态cut_to()
: 直接切换到新状态1impl Animator {
2 // 硬切换到指定状态
3 pub fn cut_to(&mut self, cx: &mut Cx, state_pair: &[LiveId; 2], index: usize, nodes: &[LiveNode]) {
4 // 获取当前状态或创建新状态
5 let mut state = self.swap_out_state().unwrap_or(Vec::new());
6 let track = state_pair[0];
7
8 // 初始化状态结构
9 if state.len() == 0 {
10 state.push_live(live!{
11 tracks: {},
12 state: {}
13 });
14 }
15
16 // 更新轨道状态
17 state.replace_or_insert_last_node_by_path(0, &[live_id!(tracks).as_field(), track.as_field()],
18 live_object!{
19 [track]: {state_id: (state_pair[1]), ended: 1}
20 }
21 );
22
23 // 应用状态值
24 let mut path = Vec::new();
25 path.push(live_id!(state).as_field());
26
27 // ... 读取和应用状态值的代码 ...
28
29 self.swap_in_state(state);
30 }
31
32 // 动画过渡到指定状态
33 pub fn animate_to(&mut self, cx: &mut Cx, state_pair: &[LiveId; 2], index: usize, nodes: &[LiveNode]) {
34 // 类似 cut_to,但会创建插值动画
35 let mut state = self.swap_out_state().unwrap_or(Vec::new());
36
37 // ... 设置动画轨道
38 state.replace_or_insert_last_node_by_path(0, &[live_id!(tracks).as_field(), track.as_field()],
39 live_object!{
40 [track]: {
41 state_id: (state_pair[1]),
42 ended: 0,
43 time: void
44 }
45 }
46 );
47
48 // ... 创建插值动画
49
50 self.swap_in_state(state);
51 self.next_frame = cx.new_next_frame();
52 }
53}
1impl Animator {
2 pub fn handle_event(&mut self, cx: &mut Cx, event: &Event) -> AnimatorAction {
3 if let Event::NextFrame(nf) = event {
4 // 检查是否是我们的下一帧
5 if !nf.set.contains(&self.next_frame) {
6 return AnimatorAction::None
7 }
8
9 // 更新所有活跃的动画轨道
10 let state_nodes = self.state.as_mut().unwrap();
11 let mut ended = true;
12 let mut redraw = false;
13
14 // ... 遍历并更新所有动画轨道
15
16 // 如果还有动画在运行,继续请求下一帧
17 if !ended {
18 self.next_frame = cx.new_next_frame();
19 }
20
21 return AnimatorAction::Animating {redraw}
22 }
23 AnimatorAction::None
24 }
25}
1impl Animator {
2 fn update_timeline_value(
3 cx: &mut Cx,
4 index: usize,
5 nodes: &mut [LiveNode],
6 ext_time: f64
7 ) -> (bool, bool) {
8 // 提取关键帧
9 let mut prev_kf: Option<KeyFrame> = None;
10
11 // 计算当前时间点的插值
12 for key_frame in key_frames {
13 if time >= prev_kf.time && time <= key_frame.time {
14 let normalized_time = (time - prev_kf.time) /
15 (key_frame.time - prev_kf.time);
16
17 // 应用缓动函数
18 let mix = key_frame.ease.map(normalized_time);
19
20 // 根据值类型进行插值
21 let new_val = match (prev_kf.value, key_frame.value) {
22 // 数值插值
23 (LiveValue::Float64(a), LiveValue::Float64(b)) => {
24 LiveValue::Float64((b - a) * mix + a)
25 },
26 // 颜色插值
27 (LiveValue::Color(a), LiveValue::Color(b)) => {
28 LiveValue::Color(Vec4::from_lerp(
29 Vec4::from_u32(a),
30 Vec4::from_u32(b),
31 mix as f32
32 ).to_u32())
33 },
34 // ... 其他类型插值
35 };
36
37 // 更新当前值
38 nodes[last_child_index].value = new_val;
39 return (ended, redraw)
40 }
41 }
42 }
43}
动画轨道系统允许多个属性独立动画