1// 1. 首先声明组件结构
2#[derive(Live, Widget)]
3pub struct TypingAnimation {
4 #[deref] view: View, // 基础视图组件
5 #[animator] animator: Animator, // 动画控制器
6
7 // 动画参数
8 #[live(0.65)] animation_duration: f64,
9 #[live(10.0)] swing_amplitude: f64,
10 #[live(3.0)] swing_base: f64,
11
12 // 运行时状态
13 #[rust] next_frame: Option<NextFrame>,
14 #[rust] animation_start_time: f64,
15 #[rust] is_animating: bool,
16}
这个结构为我们的动画组件提供了必要的基础设施。
在 Makepad 中,我们使用 live_design!
宏来声明动画状态:
1live_design! {
2 TypingAnimation = {{TypingAnimation}} {
3 // 基础属性配置
4 width: Fit,
5 height: Fit,
6
7 // 动画状态定义
8 animator: {
9 // 动画状态组
10 circle1 = {
11 default: down, // 默认状态
12 down = { // 向下移动状态
13 redraw: true,
14 from: {all: Forward {duration: 0.325}}
15 ease: InOutQuad
16 apply: {content = { circle1 = { margin: {top: 10.0} }}}
17 }
18 up = { // 向上移动状态
19 redraw: true,
20 from: {all: Forward {duration: 0.325}}
21 ease: InOutQuad
22 apply: {content = { circle1 = { margin: {top: 3.0} }}}
23 }
24 }
25 }
26 }
27}
在 Makepad 的 Live DSL 中,animator
是一个特殊的属性,用于定义组件的动画状态和行为。让我们深入了解它的语法和结构。
1animator: {
2 circle1_position = { // 位置的 track
3 default: down,
4 down = { /* 控制位置的动画状态 */ }
5 }
6 circle1_opacity = { // 透明度的 track
7 default: visible,
8 hidden = { /* 控制透明度的动画状态 */ }
9 }
10}
1// from
2from: {
3 all: Forward {duration: 0.2} // 正向播放一次
4 all: Reverse { // 反向播放一次
5 duration: 0.2,
6 end: 1.0
7 }
8 all: Loop { // 循环播放
9 duration: 0.2,
10 end: 1.0
11 }
12 all: BounceLoop { // 来回循环播放
13 duration: 0.2,
14 end: 1.0
15 }
16 all: Snap // 瞬间切换,无动画
17}
18// ease
19// - `OutQuad`/`OutCubic`: 适用于自然运动
20// - `InOutQuad`: 适用于可逆动画
21// - `Linear`: 适用于旋转等持续动画
22ease: Linear // 线性
23ease: InQuad // 二次方加速
24ease: OutQuad // 二次方减速
25ease: InOutQuad // 二次方加速减速
26ease: Bezier { // 贝塞尔曲线
27 cp0: 0.0,
28 cp1: 0.0,
29 cp2: 1.0,
30 cp3: 1.0
31}
32
33// apply
34apply: {
35 opacity: 1.0,
36 scale: 1.2,
37 color: #f00,
38 position: vec2(100.0, 0.0)
39}
动画的核心在于状态管理和帧更新:
1impl Widget for TypingAnimation {
2 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3 match event {
4 Event::NextFrame(ne) => {
5 // 处理动画帧更新
6 if let Some(next_frame) = self.next_frame {
7 if ne.set.contains(&next_frame) {
8 self.update_animation(cx, ne.time);
9 self.next_frame = Some(cx.new_next_frame());
10 }
11 }
12 }
13 // ... 其他事件处理
14 }
15 }
16}
注意: Makepad 中实现动画要基于 NextFrame ,而非 Timer。因为 Timer 依赖于底层 OS API ,有跨平台的风险。
在代码中触发动画的方法:
1// 瞬间切换状态
2self.animator_cut(cx, &[id!(hover), id!(on)]);
3
4// 播放动画过渡到状态
5self.animator_play(cx, &[id!(hover), id!(on)]);
6
7// 根据条件切换状态
8self.animator_toggle(
9 cx,
10 is_hovered, // 条件
11 Animate::Yes, // 是否需要动画
12 &[id!(hover), id!(on)], // 条件为真时的状态
13 &[id!(hover), id!(off)] // 条件为假时的状态
14);
处理动画事件:
1fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
2 // 处理动画系统事件
3 if self.animator_handle_event(cx, event).must_redraw() {
4 // 动画需要重绘
5 self.redraw(cx);
6 }
7
8 // 检查动画状态
9 if self.animator_in_state(cx, &[id!(hover), id!(on)]) {
10 // 当前处于悬停状态
11 }
12}
为动画组件提供控制接口是良好实践:
1impl TypingAnimationRef {
2 // 启动动画
3 pub fn start(&self, cx: &mut Cx) {
4 if let Some(mut inner) = self.borrow_mut() {
5 inner.start(cx);
6 }
7 }
8
9 // 停止动画
10 pub fn stop(&self, cx: &mut Cx) {
11 if let Some(mut inner) = self.borrow_mut() {
12 inner.stop(cx);
13 }
14 }
15
16 // 配置动画参数
17 pub fn set_animation_speed(&self, cx: &mut Cx, duration: f64) {
18 if let Some(mut inner) = self.borrow_mut() {
19 inner.animation_duration = duration;
20 }
21 }
22}