RippleHelper - Material 涟漪效果助手¶
RippleHelper 是 Material Design 水波纹效果的完整实现。当用户点击 Material 控件时,从点击位置扩散出的圆形涟漪能提供即时的触觉反馈。我们选择单独实现这个组件,是因为 Qt 的默认效果反馈太"僵硬",而 Material 的涟漪需要精确的扩散半径计算和淡入淡出时序控制。
效果原理¶
Material 涟漪效果的本质是一个从点击中心向外扩散的圆形渐变。按下时涟漪从零半径扩展到最大半径(覆盖整个控件),松开时透明度逐渐淡出直到消失。扩散和淡出两个动画同时进行,松开越早淡出越明显。
最大半径的计算方式是从点击中心到控件最远角点的距离,这样确保无论点击哪里,涟漪都能完全覆盖控件:
float maxRadius(const QRectF& rect, const QPointF& center) {
// 计算到四个角点的距离,取最大值
float d1 = std::hypot(center.x() - rect.topLeft().x(), center.y() - rect.topLeft().y());
float d2 = std::hypot(center.x() - rect.topRight().x(), center.y() - rect.topRight().y());
float d3 = std::hypot(center.x() - rect.bottomLeft().x(), center.y() - rect.bottomLeft().y());
float d4 = std::hypot(center.x() - rect.bottomRight().x(), center.y() - rect.bottomRight().y());
return std::max({d1, d2, d3, d4});
}
基本用法¶
在控件构造函数中初始化 RippleHelper,需要传入动画工厂:
#include "widget/material/base/ripple_helper.h"
using namespace cf::ui::widget::material;
class MyWidget : public QWidget {
public:
MyWidget(QWidget* parent = nullptr) : QWidget(parent) {
// 获取动画工厂
auto animationFactory = cf::WeakPtr<components::material::CFMaterialAnimationFactory>::DynamicCast(
Application::animationFactory()
);
// 创建涟漪助手
m_rippleHelper = new base::RippleHelper(animationFactory, this);
// 设置涟漪颜色(通常使用控件的主色调)
m_rippleHelper->setColor(cf::ui::base::CFColor::fromRGB(100, 100, 255));
// 监听重绘请求
connect(m_rippleHelper, &base::RippleHelper::repaintNeeded,
this, QOverload<>::of(&MyWidget::update));
}
private:
base::RippleHelper* m_rippleHelper;
};
事件处理¶
将鼠标事件转发给 RippleHelper 来驱动涟漪动画:
void MyWidget::mousePressEvent(QMouseEvent* event) {
QWidget::mousePressEvent(event);
m_rippleHelper->onPress(event->pos(), rect());
update();
}
void MyWidget::mouseReleaseEvent(QMouseEvent* event) {
QWidget::mouseReleaseEvent(event);
m_rippleHelper->onRelease();
update();
}
onCancel() 用于取消未释放的涟漪,比如鼠标拖出控件范围时:
void MyWidget::leaveEvent(QEvent* event) {
QWidget::leaveEvent(event);
m_rippleHelper->onCancel(); // 清除所有涟漪
update();
}
⚠️ onCancel() 会立即清除所有涟漪,不做淡出动画。这是有意为之的设计——当用户明确"取消"交互时,不需要延迟反馈。
渲染模式¶
RippleHelper 支持两种渲染模式,区别在于是否裁剪涟漪到控件边界:
enum class Mode {
Bounded, // 涟漪被裁剪到控件形状内(默认)
Unbounded // 涟漪可以超出控件边界
};
// 设置模式
m_rippleHelper->setMode(base::RippleHelper::Mode::Bounded);
大多数控件应该使用 Bounded 模式,让涟漪限制在圆角矩形内。Unbounded 模式适用于特殊场景,比如浮动操作按钮(FAB)的涟漪可以扩散到圆形边界外。
绘制涟漪¶
在 paintEvent 中,涟漪应该绘制在状态层之上、内容之下:
void MyWidget::paintEvent(QPaintEvent* event) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// 绘制背景
p.fillPath(shape, backgroundColor());
// 绘制状态层(如果有的话)
// drawStateLayer(p, shape);
// 绘制涟漪
QPainterPath clipPath;
clipPath.addRoundedRect(rect(), cornerRadius, cornerRadius);
m_rippleHelper->paint(&p, clipPath);
// 绘制内容
// drawContent(p);
}
颜色设置¶
涟漪颜色通常使用控件内容颜色(label color)的半透明版本:
// 从主题获取颜色
auto* theme = getTheme();
auto* colors = static_cast<MaterialColorScheme*>(theme->color_scheme());
QColor labelColor = colors->queryExpectedColor("md.onSurface");
// 设置涟漪颜色
m_rippleHelper->setColor(cf::ui::base::CFColor(labelColor));
在浅色主题上使用深色涟漪,深色主题上使用浅色涟漪,这是确保对比度的基本要求。
渲染细节¶
RippleHelper 内部使用径向渐变(QRadialGradient)绘制涟漪,中心实心、边缘柔和渐变到透明:
// 内部实现(简化)
QRadialGradient gradient(ripple.center, ripple.radius);
gradient.setColorAt(0.0f, color); // 中心完全实色
gradient.setColorAt(0.7f, color); // 70% 半径处仍是实色
gradient.setColorAt(1.0f, transparent); // 边缘渐变到透明
这种处理避免了锯齿边缘,使涟漪看起来更自然。
性能考虑¶
涟漪效果依赖动画工厂,如果全局动画被禁用,onPress() 会直接返回,不创建任何涟漪:
// 禁用所有动画(包括涟漪)
auto factory = Application::animationFactory();
if (factory) {
factory->setEnabledAll(false);
}
这对于低端设备或性能敏感的场景很有用。另外,hasActiveRipple() 可以用来判断是否需要重绘,避免无效的 paintEvent 调用。
动画名称¶
RippleHelper 使用动画工厂中注册的以下动画:
| 名称 | 用途 | 范围 |
|---|---|---|
md.animation.rippleExpand |
涟漪扩散 | 0.0 → 1.0 |
md.animation.rippleFade |
涟漪淡出 | 1.0 → 0.0 |
确保动画工厂中注册了这两个动画,否则涟漪不会显示。