概述及关键概念

动画系统概述

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}

这个结构看起来很简单,但它实际上是一个非常精巧的设计:

  1. live_ptr - 保存了指向 Live DSL 中动画定义的引用,使得 Animator 可以在需要时查找和加载动画状态定义
  2. state - 存储当前正在执行的动画状态,包括所有正在进行的插值计算
  3. next_frame - 用于调度下一帧动画的渲染

动画状态管理

这个系统的工作方式是基于状态转换的:

  1. 每个状态由一对 LiveId 标识符来标识([LiveId; 2])
  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}

动画轨道(track)系统

动画轨道系统允许多个属性独立动画