人文关怀:无障碍功能最佳实践
前言
本文主要总结了常见的Android无障碍适配场景&适配技巧,便于你快速进行无障碍的功能适配。
什么是无障碍功能
❝我国现在
❞1691万
视障人士,2780万
听障人士,2977万
肢体残障人士,数据来源于 「2021年第7次」 人口普查。
对于一些视障/听障人群而言,如需正常使用App功能需借助系统附带的屏幕阅读器 TalkBack
来进行辅助使用。
而TalkBack
则是集成无障碍功能和服务来提高应用对于视障/听障人群的易用性
官方支持
Google 官方对于适配无障碍功能有专门的网站及文档用于描述:
Android原生UI-构建无障碍功能更出色的应用 Compose - 对于无障碍的支持 GoogleIO-2021-无障碍分场
开发准则
在进行无障碍功能适配开发时,需遵循以下准则:
遵循 Material
中的无障碍开发规则为 非Text
类的View
增加合适的contentDescription
对于一些 装饰性的UI元素
去掉标签及焦点EditText
通过hint
设置标签比较复杂的页面采用 分组聚集
的方式对 「自定义」 的 View 进行无障碍适配
适配技巧
下面总结了常见的Android无障碍适配场景&适配技巧,便于你快速进行无障碍的功能适配。主要包括:
为View增加描述 使用ImageBtn替代Image 改造非标准组件的选中状态 手动发送无障碍事件 增加按钮触摸范围 处理焦点 兼容自定义View 无障碍适配测试
技巧1:为View增加描述
对于继承自 TextView
类的组件,Android框架本身可以读出文本的信息,所以一般情况下,我们无需再次手动适配,我们主要需要适配的是哪些Image
或者 「无法描述」 的一类组件。
如下所示,增加相应的描述即可。
// xml
android:contentDescription="xxx"
// kotlin
view.contentDescription="xxx"
技巧2:使用ImageBtn替代Image
对于按钮(「即可以交互的一些行为」),Android官方建议使用 ImageButton
替代普通的 ImageView
。相信不少同学在定义自己Bar时,肯定使用的 Image
作为返回按钮,这也是很常见的,但为什么官方建议大家使用 ImageButton
呢?
主要是因为在适配无障碍模式时,无障碍服务在读取到 Image
时,如果此时增加了描述信息,则会 「直接读出文本名字」 ,但如果此时这是一个可以交互的按钮呢?对于我们普通用户而言,大家知道这里可以点击,但是他们并不知道,所以在这里如果使用 ImageButton
,此时在无障碍下的反馈就是:xxxApp,返回 「按钮」。双击进入下一步
对于视障用户而言,这将提高他们的使用便利度,以方便他们的使用。
技巧3:改造非标准组件的选中状态
<img src="https://tva1.sinaimg.cn/large/008i3skNly1gynrjs3spvj30ia0a0jrl.jpg" alt="image-20220123180508158" style="zoom:33%;" />
类似上述代码,如果这里的选择框使用的是 ImageView
去定义,此时无障碍服务将无法识别当前相应的状态。
如果使用系统默认的组件,如 CheckBox
或者 Switch
,则可以正确读出相应状态,如果因为业务等相关问题无法直接调整,可以通过手动添加无障碍代理的方式,间接的为控件增加无障碍下的状态,如下代码所示:
view.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfo?
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info?.isCheckable = true
// 设置选择状态
info?.isChecked = isSelect
}
}
技巧4:手动发送无障碍事件
但某些情况下,我们不可能每次都像上述那要去设置吧,每次点击开关时,都走一遍上述设置代理的逻辑吧,那的确挺不优雅的,比如在某个 「RecyclerView」 中,我们甚至总不能自己维护一个选择状态吧。那么有没有其他方式,当我点击开关时,「手动去通知」 更新当前无障碍下的 [选择] 状态呢?
当然是可以的,我们通过 View.sendAccessibilityEvent(type)
手动发送一个通知事件,支持的事件类型如下:
// 具体在 AccessibilityEvent 类中
public static final int TYPE_VIEW_CLICKED = 0x00000001;
public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
public static final int TYPE_VIEW_SELECTED = 0x00000004;
public static final int TYPE_VIEW_FOCUSED = 0x00000008;
public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
...
「使用方式示例:」
比如我们使用 「view」.sendAccessibilityEvent(TYPE_VIEW_SELECTED)
,那么就可以在自定义的无障碍代理 onPopulateAccessibilityEvent()
方法中获得监听,从而更改配置,具体如下所示:
setAccessibilityDelegate(new AccessibilityDelegate() {
@Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(host, event);
int type = event.getEventType();
// type取决于你sendAccessibilityEvent(type)时
if (type == AccessibilityEvent.TYPE_VIEW_SELECTED || type == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
//提示,可以使用host.getTag(),将数据保存在view的tag中
event.getText().add("复选框" + "自定义逻辑");
}
}
});
技巧5:增加按钮触摸范围
在MD的设计中,按钮的可触摸范围至少为 「48dpx48dp」 ,所以如果我们的按钮大小不足,则可以使用下述方向进行优化:
使用 padding
为按钮图标周围增加填充使用 touchDelegate
,具体详见 读源码长知识 | 原来可以这样扩大 View 点击区域
技巧6:处理焦点
对于部分 View ,我们可能并不想在无障碍下被读取,或者需要将一些view进行 合并
,此时就可以为其增加 importantForAccessibility
属性,默认提供了以下几个选项:
<attr name="importantForAccessibility" format="integer">
<!--默认行为,系统根据其子view的类型自动判断是否对其进行读取-->
<enum name="auto" value="0" />
<!--允许在无障碍模式下访问-->
<enum name="yes" value="1" />
<!--禁止在无障碍模式下访问-->
<enum name="no" value="2" />
<!--表示其子view都禁止无障碍模式下访问-->
<enum name="noHideDescendants" value="4" />
</attr>
「示例:」
<img src="https://tva1.sinaimg.cn/large/008i3skNly1gymtv7xhugj30ic0c63z3.jpg" alt="image-20220122223943594" style="zoom:50%;" />
比如上述示例,如果没加特别描述,默认情况下,点击时,红框里只会有 「微信好友(TextView)」 在无障碍模式下触发,这显然不是我们希望的,此时就可以通过将 contentDescription
上移到外部红框的 ViewGroup
中。相应的,某些业务规则下,如果并不想其在无障碍下被选中,比如 [微博] 此时如果没有安装,则可以 「忽略其焦点」 及 「禁用」 在无障碍下的可访问性:
// xml
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no"
// kotlin
view.isFocusable = false
view.isFocusableInTouchMode = false
view.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
技巧7:兼容自定义View
我们的项目中多多少少都会存在很多自定义View,有些是基于View,有些是基于 ViewGroup 的组合效果。那么如何对自定义View做兼容呢?只需要增加相应的 contentDescription
即可,相应所有View页都可以去调用 「setContentDescription()」 。
需要注意的是,如果你自定义的 View
是一个 「按钮」 功能 ,而且没有继承自 ImageButton
或者 Button
,那么此时如果只是增加 contentDescription
标签描述,那么当view在无障碍下点击时,则只会读取描述
,而使用了 ImageButton
或者 Button
的在无障碍模式下会被读作xx 「[按钮]」 ,相比起来,后者更象征着这具有一个行为作用,而前者仅仅像一个普通文本,这对视障用户而言,体验将影响很多。
「所以我们要如何快速的兼容呢?」如果你注意观察ImageButton与Image之间的区别,你就会发现
<img src="https://tva1.sinaimg.cn/large/008i3skNly1gyklxo0x25j30ze0u00xs.jpg" alt="image-20220121003411236" style="zoom:33%;" />
「getAccessibilityClassName()」,我们只需要返回相应的 Class Name
即可。所以如果你的某个 View 具有 「行为」 作用,或者代表着是一个自定义的 「按钮」 ,那么就可以重写你所自定义View的这个方法,返回 Button
,或者 ImageButton
,这样在无障碍模式下,其就会被系统判断为是一个具有交互作用的按钮。
更多关于自定义View的适配,可以查看Android官方文档-让自定义视图使用起来更没有障碍,里面主要是讲了通过无障碍代理类来实现。
技巧8:测试无障碍适配
相应的,Google 也提供了一些工具用于查看你的适配是否合理。如无障碍功能扫描仪使用文档。
无障碍功能扫描仪主要用于:对当前屏幕上所有的 View
进行扫描,并给出建议,主要包括:
内容标签 触目目标的尺寸 是否存在可点按的内容 文本和图片的对比度
我们可以用其作为一个参考作用来使用。如:
<img src="https://tva1.sinaimg.cn/large/008i3skNly1gyns2o3bnaj317w0oq0y3.jpg" alt="image-20220123182316744" style="zoom:33%;" />
其会自动将一些认为可以优化的 View
标注出来,有些是触摸按钮太小,有些是对比度不够,在开发过程中,我们可以借此来实现快速调整。
本文主要来自读者投稿:Peterp,原文链接:https://juejin.cn/post/7056349393879990286
「Carson每天带你学习一个Android知识点」,点击下方关注;期待您文章的投稿:真诚邀请您来分享
最后福利:学习资料赠送

福利:本人亲自整理的「Android学习资料」 数量:10名 参与方式:「点击文章右下角”在看“并回复截图到公众号,将随机抽取」 点击就能升职、加薪水!