Motion Token 字面量¶
cfmaterial_motion_token_literals.h 定义了 Material Design 3 动效系统的全部 Token 字面量。Material 的动效不仅仅是"让东西动起来",而是一套精心设计的时间(Duration)和缓动(Easing)体系,用来传达 UI 的层级关系和交互反馈。
这套字面量让我们可以用语义化的方式引用动效参数,而不是到处散落 animate(300ms, ease-out) 这种魔法数字。统一管理动效参数的好处是,调整动效风格时只需改主题配置,整个应用的动画节奏会保持一致。
动效时长 Token¶
Material 定义了 6 类标准动效时长,按元素尺寸和进出方向划分:
#include "ui/core/token/motion/cfmaterial_motion_token_literals.h"
using namespace cf::ui::core::token::literals;
// 小元素进入 - 200ms
const char* shortEnterDuration = MOTION_SHORT_ENTER_DURATION;
// "md.motion.shortEnter.duration"
// 小元素退出 - 150ms
const char* shortExitDuration = MOTION_SHORT_EXIT_DURATION;
// "md.motion.shortExit.duration"
// 中等元素进入 - 300ms
const char* mediumEnterDuration = MOTION_MEDIUM_ENTER_DURATION;
// 中等元素退出 - 250ms
const char* mediumExitDuration = MOTION_MEDIUM_EXIT_DURATION;
// 大元素进入 - 450ms
const char* longEnterDuration = MOTION_LONG_ENTER_DURATION;
// 大元素退出 - 400ms
const char* longExitDuration = MOTION_LONG_EXIT_DURATION;
// 状态变化 - 200ms
const char* stateChangeDuration = MOTION_STATE_CHANGE_DURATION;
// 涟漪展开 - 400ms
const char* rippleExpandDuration = MOTION_RIPPLE_EXPAND_DURATION;
// 涟漪消散 - 150ms
const char* rippleFadeDuration = MOTION_RIPPLE_FADE_DURATION;
注意退出时长通常比进入时长短,这是个刻意的设计——用户等待东西出现比等待东西消失更不耐烦。
动效缓动 Token¶
缓动函数决定了动画随时间的变化速率,Material 定义了三类标准缓动:
// 小元素进入缓动 - EmphasizedDecelerate
const char* shortEnterEasing = MOTION_SHORT_ENTER_EASING;
// "md.motion.shortEnter.easing"
// 小元素退出缓动 - EmphasizedAccelerate
const char* shortExitEasing = MOTION_SHORT_EXIT_EASING;
// 中等元素进入缓动 - EmphasizedDecelerate
const char* mediumEnterEasing = MOTION_MEDIUM_ENTER_EASING;
// 中等元素退出缓动 - EmphasizedAccelerate
const char* mediumExitEasing = MOTION_MEDIUM_EXIT_EASING;
// 大元素进入/退出缓动 - Emphasized
const char* longEnterEasing = MOTION_LONG_ENTER_EASING;
const char* longExitEasing = MOTION_LONG_EXIT_EASING;
// 状态变化缓动 - Standard
const char* stateChangeEasing = MOTION_STATE_CHANGE_EASING;
// 涟漪展开缓动 - Standard
const char* rippleExpandEasing = MOTION_RIPPLE_EXPAND_EASING;
// 涟漪消散缓动 - Linear
const char* rippleFadeEasing = MOTION_RIPPLE_FADE_EASING;
缓动函数说明¶
Material 的缓动函数名称有点抽象,这里解释一下:
| 缓动名称 | 数学特性 | 适用场景 | 视觉效果 |
|---|---|---|---|
| EmphasizedDecelerate | 快进慢出 | 元素进入 | 快速入场后柔和减速 |
| EmphasizedAccelerate | 慢进快出 | 元素退出 | 缓慢启动后快速离开 |
| Emphasized | 先加速再减速(明显) | 大元素进入 | 起步和结束都柔和,中间加速明显 |
| Standard | 先加速再减速(轻微) | 状态变化 | 轻微的缓动,不干扰视线 |
| Linear | 匀速 | 涟漪消散 | 无缓动,适合淡出效果 |
Emphasized 系列用于大尺寸元素,是因为大元素的移动更明显,需要更柔和的缓动来避免视觉冲击。Standard 用于状态变化(如按钮按下),不希望太夸张。
在主题系统中使用¶
动效 Token 的使用方式和其他 Token 一致,由主题系统解析成实际的时长和缓动:
// 伪代码:主题系统如何解析动效 Token
struct MotionSpec {
int durationMs;
EasingFunction easing;
};
class MaterialTheme {
MotionSpec resolveMotion(const char* durationToken,
const char* easingToken) const {
return {
durationTable.at(durationToken),
easingTable.at(easingToken)
};
}
};
// 使用示例
MaterialTheme theme;
// 小元素进入动画
auto fadeIn = theme.resolveMotion(
MOTION_SHORT_ENTER_DURATION,
MOTION_SHORT_ENTER_EASING
);
element.animate(fadeIn);
// 对话框进入动画(大元素)
auto dialogEnter = theme.resolveMotion(
MOTION_LONG_ENTER_DURATION,
MOTION_LONG_ENTER_EASING
);
dialog.animate(dialogEnter);
场景选择指南¶
选择哪个动效参数组合,主要看元素尺寸和动效类型:
// 小元素(按钮、开关、标签)
// 进入: 200ms + EmphasizedDecelerate
// 退出: 150ms + EmphasizedAccelerate
auto smallEnter = resolveMotion(
MOTION_SHORT_ENTER_DURATION,
MOTION_SHORT_ENTER_EASING
);
// 中等元素(卡片、列表项)
// 进入: 300ms + EmphasizedDecelerate
// 退出: 250ms + EmphasizedAccelerate
auto mediumEnter = resolveMotion(
MOTION_MEDIUM_ENTER_DURATION,
MOTION_MEDIUM_ENTER_EASING
);
// 大元素(对话框、抽屉)
// 进入: 450ms + Emphasized
// 退出: 400ms + Emphasized
auto largeEnter = resolveMotion(
MOTION_LONG_ENTER_DURATION,
MOTION_LONG_ENTER_EASING
);
// 状态变化(按钮按下、切换开关)
// 200ms + Standard
auto stateChange = resolveMotion(
MOTION_STATE_CHANGE_DURATION,
MOTION_STATE_CHANGE_EASING
);
// 涟漪效果
// 展开: 400ms + Standard
// 消散: 150ms + Linear
auto rippleExpand = resolveMotion(
MOTION_RIPPLE_EXPAND_DURATION,
MOTION_RIPPLE_EXPAND_EASING
);
⚠️ 一个常见的错误是用错缓动方向。进入动画应该用 Decelerate(减速),这样元素到达终点时会柔和地停下来;退出动画应该用 Accelerate(加速),元素快速离开不留痕迹。用反了会感觉很怪——进入时"哐"一下撞上终点,退出时拖泥带水。
批量遍历¶
提供了遍历所有动效 Token 的辅助:
// 遍历所有 Motion Token
for (size_t i = 0; i < MOTION_TOKEN_COUNT; ++i) {
printf("Motion Token %zu: %s\n", i, ALL_MOTION_TOKENS[i]);
}
自定义动效参数¶
如果你的设计需要非标准的动效参数,有两种处理方式:
// 方式 1:在主题配置中添加自定义 Token
// 保持代码语义化,但需要修改主题系统
// 方式 2:直接构造 MotionSpec(灵活但不统一)
// 对于特殊场景,直接传值更实际
MotionSpec custom = {500, CustomEasing};
element.animate(custom);
建议把常用的自定义动效也纳入 Token 管理,保持代码的一致性。
性能考虑¶
动效虽然是视觉体验的重要组成部分,但也要注意性能:
// 硬件加速是个好主意
element.setLayerType(LAYER_TYPE_HARDWARE);
element.animate(spec);
// 大量元素同时动画时,考虑简化动效
// 比如用短时长代替长时长,或者取消缓动
Material 的动效时长(最大 450ms)是经过平衡的——既足够传达视觉反馈,又不会让用户等太久。如果你的动效感觉"拖",可能不是时长问题,而是缓动曲线选择不当。
可访问性¶
对于动效敏感的用户,应该提供"减少动效"选项:
// 伪代码:尊重用户的动效偏好
if (userPrefersReducedMotion()) {
// 跳过动画,或者使用极短时长
element.setOpacity(1.0f); // 直接设置最终状态
} else {
// 正常动画
element.animate(spec);
}
Material 的动效设计已经考虑了可访问性,但提供降级选项仍然是好实践。