焦点指示器——无障碍访问的视觉实现¶
在前面的文章里,我们聊了状态机、涟漪效果和海拔阴影。这些组件主要响应鼠标交互。但对于键盘导航和无障碍访问来说,还需要一个重要的视觉元素——焦点指示器。
这篇文章聊聊 MdFocusIndicator 的设计。
Material 焦点环规范¶
Material Design 3 对焦点指示器有明确的规范:
- 宽度:3dp
- 内边距:3dp(距离控件边界)
- 动画:淡入淡出效果
- 颜色:通常是 Primary 或 OnPrimary 颜色
焦点环只在控件获得键盘焦点时显示,为键盘用户提供清晰的视觉反馈。
MdFocusIndicator 的设计¶
MdFocusIndicator 是一个轻量级组件,只负责绘制焦点环:
class MdFocusIndicator : public QObject {
public:
explicit MdFocusIndicator(
cf::WeakPtr<components::material::CFMaterialAnimationFactory> factory,
QObject* parent = nullptr);
void onFocusIn();
void onFocusOut();
void paint(QPainter* painter, const QRectF& widgetRect, float cornerRadius);
private:
float m_progress = 0.0f; // 0 = 隐藏,1 = 完全显示
cf::WeakPtr<components::material::CFMaterialAnimationFactory> m_animator;
};
焦点进入/离开¶
当控件获得焦点时,焦点环淡入;失去焦点时,焦点环淡出:
void MdFocusIndicator::onFocusIn() {
auto anim = m_animator->getAnimation("md.animation.fadeIn");
if (anim) {
connect(anim.get(), &ICFAbstractAnimation::progressChanged,
this, [this](float progress) {
m_progress = progress;
// 触发重绘
});
anim->start();
} else {
m_progress = 1.0f;
}
}
void MdFocusIndicator::onFocusOut() {
auto anim = m_animator->getAnimation("md.animation.fadeOut");
if (anim) {
connect(anim.get(), &ICFAbstractAnimation::progressChanged,
this, [this](float progress) {
m_progress = 1.0f - progress; // 反向
});
anim->start();
} else {
m_progress = 0.0f;
}
}
注意淡出动画使用的是 1.0f - progress,因为我们希望透明度从 1 变到 0。
焦点环的绘制¶
焦点环是一个圆角矩形,位于控件边界外侧 3dp 的位置:
void MdFocusIndicator::paint(QPainter* painter, const QRectF& widgetRect,
float cornerRadius) {
if (m_progress <= 0.001f) {
return; // 透明度为 0,跳过绘制
}
CanvasUnitHelper helper(qApp->devicePixelRatio());
float inset = helper.dpToPx(3.0f);
float width = helper.dpToPx(3.0f);
// 计算焦点环矩形
QRectF ringRect = widgetRect.adjusted(-inset, -inset, inset, inset);
// 创建圆角矩形路径
QPainterPath ringPath;
ringPath.addRoundedRect(ringRect, cornerRadius + inset, cornerRadius + inset);
// 设置颜色和透明度
QColor ringColor = /* 从主题获取 */;
ringColor.setAlphaF(m_progress * ringColor.alphaF());
// 绘制
painter->save();
QPen pen(ringColor, width);
pen.setCosmetic(true); // 不受变换影响
painter->setPen(pen);
painter->setBrush(Qt::NoBrush);
painter->drawPath(ringPath);
painter->restore();
}
与控件的集成¶
控件需要在焦点事件中调用 MdFocusIndicator:
void Button::focusInEvent(QFocusEvent* event) {
QPushButton::focusInEvent(event);
m_focusIndicator->onFocusIn();
}
void Button::focusOutEvent(QFocusEvent* event) {
QPushButton::focusOutEvent(event);
m_focusIndicator->onFocusOut();
}
void Button::paintEvent(QPaintEvent* event) {
// ... 其他绘制步骤
// 最后绘制焦点环
m_focusIndicator->paint(&painter, rect(), cornerRadius());
}
无障碍考虑¶
焦点指示器是无障碍访问的重要组成部分。对于使用键盘、屏幕阅读器或其它辅助技术的用户来说,焦点指示器提供了清晰的视觉反馈,帮助他们理解当前的交互位置。
Material Design 3 要求焦点指示器必须:
- 始终可见:当控件有焦点时,焦点环必须清晰可见
- 高对比度:焦点环颜色必须与背景有足够的对比度
- 动画平滑:焦点切换时的过渡应该流畅自然
性能优化¶
焦点环的绘制相对简单,性能开销不大。但我们可以做一些优化:
- 透明度为 0 时跳过绘制:避免无意义的绘制操作
- 使用 cosmetic pen:
setCosmetic(true)让线宽不受缩放影响 - 路径缓存:如果控件形状不变,可以缓存 QPainterPath
总结¶
MdFocusIndicator 是 Material 行为层的最后一个核心组件。它提供了清晰的焦点反馈,支持平滑的淡入淡出动画,是无障碍访问的重要组成部分。
到这里,Layer 4(Material Behavior Layer)的内容就基本覆盖了。我们有了状态管理、涟漪效果、海拔阴影、焦点指示器四个核心组件。
但这些组件不能独立工作——它们需要被组合到具体的控件中,才能发挥作用。
接下来,我们进入 Layer 5:Widget Adapter Layer,看看如何将这些行为组件整合到具体控件中。
相关文档