去年夏天,一位谷歌工程师写了一篇关于焦点管理的博客,却花了整整一年才完成这篇续作。他在草稿箱里反复打磨的,是一个被大多数开发者忽略的问题:键盘用户怎么知道自己在哪?
答案藏在「焦点指示器」里——那个显示当前键盘焦点位置的视觉标记。Android 默认的涟漪效果(ripple)技术上能通过无障碍标准,但肉眼几乎看不清。这篇技术博客的价值,在于它提供了一套可复用的工程方案,而非简单的代码片段。
为什么默认方案不够用
网页内容无障碍指南(WCAG)对焦点指示器有明确要求:必须足够可见。Android 的涟漪效果在深色背景上尤其容易消失,键盘用户——包括运动障碍者、高级用户、以及任何不想抬手摸屏幕的人——会在导航中迷失。
更关键的是,这位工程师明确区分了两个概念:键盘焦点指示器 vs 屏幕阅读器焦点。后者由系统(如 TalkBack)处理,前者则需要开发者自己实现。混淆这两者,是很多应用无障碍体验糟糕的根源。
常见的修复思路是给聚焦元素加边框。Appt.org 的代码示例就是这么做的。但这位工程师想要更灵活的东西:一条简单的下划线,能用在按钮上,也能用在开关行上,最好能通过 Modifier 一键接入任何交互组件。
这需要用到 Compose 的 Indication API,配合 DrawModifierNode 来自定义绘制逻辑。
三层封装的设计思路
工程师的目标很明确:把复杂度埋进 Modifier 里,让业务代码保持简洁。为此他拆解了三个必要组件:
最底层是 FocusNode 类。它继承 Modifier.Node 和 DrawModifierNode,接收两个参数:InteractionSource(感知交互状态)和 Color(指示器颜色)。核心逻辑落在两个重写方法里:onAttach 处理焦点状态的订阅,draw() 负责实际绘制。
onAttach 里用了一个 mutableStateOf(false) 存储聚焦状态,通过协程监听 interactionSource 的变化。这是 Compose 的响应式范式——状态驱动重绘,而非命令式地调用 invalidate()。
中间层需要把 FocusNode 包装成可用的 Modifier。这里用到 Compose 的 ModifierNodeElement 模式:定义一个数据类持有配置参数,create() 方法实例化 Node,update() 方法处理参数变化时的复用。这种设计让 Modifier 在重组时尽可能复用底层节点,避免不必要的对象创建。
最上层是面向开发者的 API。理想情况下,一行代码就能启用:.indication(interactionSource, FocusIndication(Color.Blue))。interactionSource 由组件自己提供,FocusIndication 是封装好的指示器工厂。
从「能用」到「好用」的工程权衡
代码实现中有几个值得注意的选择。
首先是绘制区域的处理。工程师在 draw() 方法里调用了 drawContent(),确保子组件先完成自身绘制,再在之上叠加指示器。这避免了遮挡问题,也符合「装饰器」模式的设计直觉。
其次是动画的取舍。博客示例中的下划线是即时出现的,没有淡入淡出。工程师提到可以用 Animatable 实现平滑过渡,但为了保持示例简洁并未展开。这是技术写作的典型策略:先跑通核心链路,再提示扩展方向。
最微妙的权衡在颜色参数。FocusNode 接收的是 Color 而非 Brush,这意味着渐变指示器需要额外改造。工程师没有解释这个限制,但结合 Android 无障碍最佳实践来看,纯色高对比度往往是更稳妥的选择——渐变虽然好看,可能在某些背景上失去辨识度。
为什么这件事值得开发者关注
这位工程师的博客标题用了「More Accessible」,而非「Fully Accessible」。这个措辞本身就很诚实:它只是改进,不是终点。
但改进的价值被低估了。Android 生态中,键盘导航长期是「二等公民」。Chromebook 支持 Android 应用、折叠屏外接键盘、TV 遥控器交互——这些场景都在增长,却很少有应用做好焦点管理。一个可复用的 Modifier 方案,降低了团队的实施门槛。
更深层的意义在于 API 设计哲学。Compose 的 Indication 系统把「交互反馈」抽象为可插拔的模块,开发者可以替换默认涟漪,也可以为不同场景配置不同指示器。这种扩展性,是命令式 UI 框架难以实现的。
这位工程师花一年时间打磨这篇博客,或许正是因为这个主题「不够性感」——它解决的是边缘场景,代码却涉及 Compose 较底层的 Modifier 机制。但正是这种「把脏活做干净」的工程态度,让无障碍改进从「应该做」变成「容易做」。
对于正在评估 Compose 迁移方案的团队,这个案例提供了具体的检查点:你们的焦点指示器策略是什么?能否在不污染业务代码的前提下统一升级?答案可能决定大量键盘用户的去留。
热门跟贴