Android 无障碍优化: 如何为视图添加自定义导航动作
在优化适配 Android 应用的无障碍时,Accessibility Actions(自定导航操作)是一个非常实用但很多开发者都不知道的 API。很多人误以为给控制项添加了长按功能表,就可以在 TalkBack 操作菜单项目中找到这些动作,其实不然。
实际上,自定义Actions的设计逻辑与 TalkBack 的底层交互是深度绑定的。如果不清楚 TalkBack 是如何暴露这些Actions的,写出来的程序往往达不到预期的无障碍效果。
本文结合具体场景,由浅入深带你了解 Android 自定义无障碍操作的最佳实践,希望能帮大家避开适配误区。
一、 场景分析
我们以大家最熟悉的聊天介面为例。
在消息清单中,每条消息除了“点击查看详情”/“点击放大图片”这个主交互外,通常还包含一堆附加操作功能表,复制、转发、删除、引用回复等。对於健视使用者来说,处理这些操作的方式非常直观:长按消息,弹出上下文功能表,点击目标功能表项。
但视障用户面对这个交互时,体验却大打折扣。TalkBack虽然也支持单指按两下按住不放触发功能表,但这十分不便。更重要的问题是长按後的体验不佳。
长按弹出的功能表是个浮层,TalkBack 用户触发长按後,焦点需要跳进这个浮层里,再逐项遍历找到“复制”,最後按两下执行。整个交互链路变成了:聚焦消息 -> 按两下长按 -> 等待菜单弹出 -> 在新出现的菜单内重新流览 -> 找到目标 -> 按两下。不仅步骤繁琐,而且使用者很难预判“长按之後会出来什么”。更糟糕的是,弹出的功能表浮层经常会发生失焦,导致用户根本不知道当前焦点落在了哪里。
面对这一问题,很多开发者会直接用一种简单粗暴的办法,把复制、转发、删除做成独立按钮,直接放在TalkBack线性流览序列中,每条消息後面,对应的是这些功能按钮。
这种做法在技术上当然可行,但在实际体验中却是一场灾难:每条消息会多出三四个焦点元素,十条消息就是四十多个焦点。视障用户每次线性遍历(单指左右滑动)都要经过这些节点。本来用户只是想快速翻到最底下看新消息,结果中间被几十个“复制”、“转发”的焦点阻碍,流览效率直接崩溃。
Android 的 Accessibility Actions 就是为了解决这个矛盾而设计的:把次级操作挂到控制项本身的 action 清单上,不增加焦点数量,不依赖视觉弹窗,让 TalkBack 用自己的原生功能表体系按需暴露这些操作。
二、 如何注入自定义导航动作
AndroidX Core 库提供了 ViewCompat.addAccessibilityAction 方法,能给视图的 AccessibilityNodeInfo(无障碍节点资讯,TalkBack 读取控制项可访问资讯的底层结构)追加操作项。
// 增加“复制”操作
ViewCompat.addAccessibilityAction(messageView, "复制") { view, args ->// 在此执行复制逻辑true // 返回 true 表示操作已被消费
}// 增加“转发”操作
ViewCompat.addAccessibilityAction(messageView, "转发") { view, args ->// 执行转发逻辑true
}// 增加“删除”操作
ViewCompat.addAccessibilityAction(messageView, "删除") { view, args ->// 执行删除逻辑true
}
上述代码,把三个操作挂在了同一个消息控制项上。当 TalkBack 聚焦这条消息时,朗读的依然是消息内容本身,绝对不会多出三个额外的焦点。这三个操作作为节点 action 列表里的条目,可让使用者通过读屏软件提供的统一途径来访问。TalkBack聚焦後,除了能听到消息,还会听到播报,如“有操作可用”。
三、 用户如何触发这些操作?
代码把操作注入了,但TalkBack 的实际表现是怎样的,用户如何触发呢?
实际上,TalkBack 提供了两个方式。
方式一:TalkBack 功能表(上下文环境功能表)
使用者聚焦到某条消息後,通过三指点按萤幕,或者单指先向下再向右滑动,即可唤起 TalkBack 功能表。
这个功能表里包含一组全域选项(如“从聚焦项开始朗读”、“萤幕搜索”等),同时会根据当前焦点动态注入上下文选项。Google 官方文档中明确提到:“执行该手势,您可能会听到与焦点对应的不同选项,例如:操作、修改选项、连结。”
这里的 “操作”,就是当前控制项上挂载的自定动作列表。用户滑动找到“操作”,按两下进入子功能表,再滑动找到“复制”,按两下即可执行。
方式二:Reading Controls(阅读控制项 / 导航模式)
阅读控制多指支持手势,是 TalkBack 9.1 正式引入的,类似於 Voiceover 的转子。用户可以通过三指上下滑动,或者单指先向上再向下/先向下再向上回圈切换导航专案(如“字元”、“单词”、“标题”、“连结”等模式)。
注:一些第三方 Android 读屏,也把 Reading Controls 称作“导航”。
当使用者将导航模式切换到 “操作”(Actions) 时,单指上下滑动就可以在当前控制项的自定操作之间回圈。TalkBack 会逐个朗读“复制”、“转发”、“删除”,用户听到想要的动作後,直接按两下即可启动。
这个设计思路与 iOS VoiceOver 的“转子(Rotor)”如出一辙。在 iOS 中,用户旋转转子到“操作”项目,单指上下滑动遍历自定义操作,按两下启动。Android 用三指滑动切换 Reading Controls 替代了 iOS 的旋转手势,核心意图是一致的:把次级操作从视觉弹窗变成辅助技术原生的可遍历操作列表。
四、 拖动操作的无障碍替代
除了消息功能表,另一个典型的交互场景是拖动排序 (比如多个扣款方式,需要设定其预设顺序)。
健视者按住清单项拖到目标位置,松手即可完成排序。但拖拽依赖精确的位置定位和连续的手势追踪,视障者看不见萤幕上的图示,几乎无法完成这个动作。而对於使用 Switch Access(通过外部开关访问介面的辅助技术)的使用者来说,物理上就根本无法拖拽。
解决方案是将连续的拖拽手势用两个自定义动作替代:“上移”和“下移”。
ViewCompat.addAccessibilityAction(listItemView, "上移") { view, args ->// 逻辑:将当前项与上一项交换位置true
}ViewCompat.addAccessibilityAction(listItemView, "下移") { view, args ->// 逻辑:将当前项与下一项交换位置true
}
这里只是演示,若在实际的 RecyclerView 中使用,受限於 ViewHolder 的复用机制,须改用 setAccessibilityDelegate 重写 onInitializeAccessibilityNodeInfo,并基於 bindingAdapterPosition 动态注入 Action,防止滑动时操作功能表无限堆积或位置状态错乱。
当使用者聚焦到目标项後,通过前面提到的方法选择“上移”或“下移”,逻辑层执行位置交换并刷新列表。
操作成功后,可通过 view.announceForAccessibility("已移至第x位") 给用户提供一句明确的回馈播报。
五、 覆盖点击提示(说明性资讯)
另外,还有一个容易混淆的点,就是覆盖默认的点击提示,它不是自定义操作,也不是将某个自定义操作变为默认点击动作。
通过 AccessibilityDelegateCompat 可以覆盖 ACTION_CLICK 动作的默认描述:
// 重写点击描述
ViewCompat.setAccessibilityDelegate(view, object : AccessibilityDelegateCompat() {override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {super.onInitializeAccessibilityNodeInfo(host, info)val action = AccessibilityNodeInfoCompat.AccessibilityActionCompat(AccessibilityNodeInfoCompat.ACTION_CLICK,"复制")info.addAction(action)}
})
这只是把 TalkBack 聚焦後读的默认提示,从“按两下即可启动”改成了“按两下即可复制”。也不会新增操作项,不会出现在 TalkBack 功能表的“操作”里,也不会出现在阅读控制项里,更不会改变按两下後实际执行的点击逻辑。
它的适用场景相对比较窄。只有当控制项的点击行为本身就是“复制”,且默认提示太笼统时,才用这种方式补充说明。
如果控制项的预设点击是“查看完整消息”,而“复制”只是个附加操作,千万不要覆盖提示。覆盖後 TalkBack 朗读“按两下即可复制”,用户按两下进去却是详情,这就反而误导用户了。
注意,这与 contentDescription 完全不同。contentDescription 描述的是控制项本身“是什麽”,比如“复制”按钮;而覆盖点击标签描述的是点击这个控制项“会做什麽”,比如“按两下即可复制”。两者可以配合使用,但作用层面完全不同。
参考资料:
- ViewCompat.addAccessibilityAction 参考
- AccessibilityNodeInfoCompat 参考
- TalkBack 功能表与阅读控制使用教程
- TalkBack 手势参考指南
- TalkBack 9.1 新增功能
作者
张赐荣,视障者,资讯无障碍解决方案资深研发专家,长期致力於数位化产品的可及性研究与用户体验优化工作,专注於为残障人士消除数字鸿沟。
曾主持过多项大型互联网产品的无障碍改造专案及发布大量原创技术指南,系统性地推动了国内无障碍技术的标准化进程与落地部署工作,是一位元在国内资讯无障碍行业具有显着影响力的领军人物。
知乎: @张赐荣
赐荣博客: www.prc.cx
