CFMaterialFadeAnimation - Material 淡入淡出动画¶
CFMaterialFadeAnimation 是 Material Design 3 运动系统中的透明度动画实现,负责按照 Material 规范控制 widget 的不透明度变化。淡入淡出是 UI 中最基础的过渡效果,我们实现这个动画不仅是为了视觉美观,更重要的是在元素出现、消失或状态切换时给用户清晰的心理预期。
在 Material Design 3 中的定位¶
Material Design 3 的 Motion System 将淡入淡出归类为"强调性进入"和"强调性退出"的标准实现。与其他动画类型相比,淡入淡出不涉及位置或尺寸变化,是最"安静"的过渡方式,适合用于:
- 模态对话框的出现和消失
- 列表项的批量加载
- 状态指示器的切换
- 与其他动画组合使用时作为"透明度维度"
基本用法¶
淡入淡出动画需要通过 IMotionSpec 获取时序参数,这个 spec 通常从主题的 MotionScheme 中获得:
#include "ui/components/material/cfmaterial_fade_animation.h"
#include "ui/core/theme.h"
using namespace cf::ui::components::material;
// 从主题获取 motion spec
auto& motionSpec = theme->motion_spec();
// 创建淡入动画
auto fadeAnim = std::make_unique<CFMaterialFadeAnimation>(&motionSpec, this);
// 设置目标 widget
fadeAnim->setTargetWidget(myDialog);
// 开始动画(Forward = 淡入,Backward = 淡出)
fadeAnim->start(Direction::Forward);
动画通过 QGraphicsOpacityEffect 作用于 widget,这意味着它适用于所有继承自 QWidget 的控件,包括那些本身不直接支持透明度属性的组件。
透明度范围¶
默认情况下,动画会在完全透明(0.0)和完全不透明(1.0)之间插值。如果你需要自定义范围(比如从半透明到完全不透明),可以通过继承或修改动画配置来实现:
// 动画内部使用 0.0 ~ 1.0 的标准范围
// 如果需要部分淡入效果,需要在动画完成后手动设置最终状态
connect(fadeAnim.get(), &ICFAbstractAnimation::finished, this, [widget]() {
// 设置为某个中间透明度值
widget->setWindowOpacity(0.8);
});
时序控制¶
动画的持续时间和缓动曲线由 MotionSpec 决定,工厂会根据动画类型自动选择合适的 motion token:
| 场景 | 推荐时长 | 推荐缓动 |
|---|---|---|
| 小元素淡入(按钮、图标) | 200ms | EmphasizedDecelerate |
| 小元素淡出 | 150ms | EmphasizedAccelerate |
| 中等元素淡入(卡片、列表项) | 250ms | EmphasizedDecelerate |
| 大元素淡入(对话框、面板) | 300ms | EmphasizedDecelerate |
与 QGraphicsOpacityEffect 的交互¶
动画会自动为 target widget 创建并管理 QGraphicsOpacityEffect。这个设计带来两个需要注意的点:
第一,如果 widget 已经有 graphics effect,动画会尝试复用它而不是覆盖。这意味着你可以在同一个 effect 上组合多个属性动画:
// widget 已有一个模糊效果
auto* blurEffect = new QGraphicsBlurEffect(widget);
widget->setGraphicsEffect(blurEffect);
// 淡入动画会创建独立的 opacity effect
// 不会影响已有的模糊效果
fadeAnim->setTargetWidget(widget);
第二,effect 的生命周期由动画管理。当动画销毁时,如果 effect 是它创建的,也会一并销毁。如果你需要在动画结束后保留最终的透明度状态,需要注意 effect 的所有权问题:
生命周期控制¶
动画支持暂停、恢复、停止和反向操作:
fadeAnim->start(Direction::Forward); // 开始淡入
// ... 动画进行中 ...
fadeAnim->pause(); // 暂停在当前状态
fadeAnim->reverse(); // 从当前状态反向播放(淡出)
fadeAnim->stop(); // 停止并重置到初始状态
reverse() 是一个很方便的操作,它会让动画从当前进度开始反向播放,而不是跳回起点。这在实现"鼠标悬停显示,移开隐藏"这类交互时特别好用。
性能考量¶
淡入淡出动画本身的开销很小,主要消耗在 QGraphicsEffect 的渲染上。如果你需要同时为大量 widget 应用淡入淡出(比如一个包含上百项的列表),考虑以下优化:
- 批量启动时使用不同的延迟,避免所有动画在同一帧更新
- 对于简单的透明度需求,考虑直接用
QWidget::setWindowOpacity()或样式表 - 在低端设备上可以通过工厂的全局开关禁用动画
常见场景¶
模态对话框淡入¶
void showModalDialog(QWidget* dialog) {
dialog->setWindowOpacity(0.0);
dialog->show();
auto fadeAnim = std::make_unique<CFMaterialFadeAnimation>(&motionSpec);
fadeAnim->setTargetWidget(dialog);
fadeAnim->start(Direction::Forward);
}
加载状态切换¶
void showLoadingIndicator() {
loadingLabel->show();
auto fadeAnim = factory->getAnimation("md.animation.fadeIn");
fadeAnim->setTargetWidget(loadingLabel);
fadeAnim->start();
}
void hideLoadingIndicator() {
auto fadeAnim = factory->getAnimation("md.animation.fadeOut");
connect(fadeAnim.get(), &ICFAbstractAnimation::finished, loadingLabel, &QWidget::hide);
fadeAnim->setTargetWidget(loadingLabel);
fadeAnim->start();
}
组合动画¶
淡入淡出常与其他动画组合使用,比如从下方滑入的同时淡入:
// 滑动动画和淡入动画同时启动
auto slideAnim = factory->getAnimation("md.animation.slideUp");
auto fadeAnim = factory->getAnimation("md.animation.fadeIn");
slideAnim->setTargetWidget(widget);
fadeAnim->setTargetWidget(widget);
slideAnim->start();
fadeAnim->start();