Accessibility测试:让每个用户都能用好你的App

说实话,我刚入行那会儿,对无障碍测试基本是忽视的。总觉得「我的App功能做完了,UI也挺好看,谁会在意那些看不见摸不着的东西?」直到有一次,我亲眼看到一个视障朋友拿着手机,用TalkBack艰难地操作一个我参与开发的App——那个体验,真的让我脸红。

从那以后,Accessibility测试就成了我项目里的必修课。今天我们就来聊聊,Android上怎么做无障碍测试。

Accessibility测试工具

工欲善其事,必先利其器。Android官方给我们准备了不少好用的工具。

1. Accessibility Scanner

这是Google官方出品的扫描工具。你不需要写一行代码,安装后打开App,它会自动扫描当前界面,然后告诉你哪里有问题。

我个人习惯在每次UI改动后,先用它扫一遍。它能检测出:

  • 内容描述缺失
  • 触摸目标太小
  • 颜色对比度不足
  • 点击区域重叠

安装方式很简单,Google Play搜「Accessibility Scanner」就行。或者你也可以用adb安装:

adb install -r AccessibilityScanner.apk

2. Espresso的AccessibilityChecks

如果你已经在用Espresso做UI测试,那加上无障碍检查几乎零成本。只需要在测试类里加一行:

@Before
fun setUp() {
    AccessibilityChecks.enable()
}

然后你所有的Espresso测试,都会自动检查无障碍问题。我在项目中遇到过,加了这行之后,原本通过的测试突然红了——原来是一个ImageView忘了加contentDescription。

3. 系统自带的TalkBack

TalkBack是Android系统内置的屏幕阅读器。你可以在「设置 → 无障碍 → TalkBack」里打开它。我建议每个开发者都亲自体验一下,关掉屏幕,只用听的方式操作你的App。那种感觉,嗯,会让你对无障碍有全新的认识。

测试内容描述

内容描述,说白了就是给那些「看不见」的用户,用语音告诉他们「这是什么」。

比如一个图片按钮,你光放个图标,视障用户摸到它,TalkBack只会说「按钮」。这谁听得懂?

正确的做法是:

<ImageButton
    android:id="@+id/btn_search"
    android:src="@drawable/ic_search"
    android:contentDescription="搜索" />

在代码里也可以动态设置:

btn_search.contentDescription = "搜索"

这里有个坑,我曾经踩过:

注意:不要把contentDescription写成「搜索按钮」。TalkBack会自动在末尾加上「按钮」两个字,所以如果你写了「搜索按钮」,它读出来就是「搜索按钮按钮」——很尴尬。

那什么时候不需要contentDescription?

  • 纯装饰性的图片,比如分割线、背景图
  • 已经有文字标签的控件

对于装饰性图片,可以这样处理:

android:contentDescription="@null"

或者用代码:

imageView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO

测试焦点导航

焦点导航,就是用户用键盘、方向键或者滑动屏幕时,焦点怎么移动。

你想想看,一个视障用户用手指在屏幕上滑动,TalkBack会逐个朗读焦点所在的元素。如果焦点顺序乱了,用户听到的内容就是东一句西一句,完全没法理解。

默认情况下,Android会按照布局的先后顺序决定焦点顺序。但有时候,这个顺序并不合理。

比如一个登录界面,布局顺序是:

  1. 用户名输入框
  2. 登录按钮
  3. 密码输入框
  4. 忘记密码链接

这显然不合理。用户应该先输入用户名,再输入密码,然后点击登录。这时候就需要手动调整焦点顺序:

<EditText
    android:id="@+id/et_username"
    android:nextFocusDown="@id/et_password"
    android:nextFocusForward="@id/et_password" />

<EditText
    android:id="@+id/et_password"
    android:nextFocusDown="@id/btn_login"
    android:nextFocusForward="@id/btn_login" />

<Button
    android:id="@+id/btn_login"
    android:nextFocusDown="@id/tv_forgot_password"
    android:nextFocusForward="@id/tv_forgot_password" />

在代码里也可以设置:

et_username.nextFocusDownId = R.id.et_password
et_password.nextFocusDownId = R.id.btn_login
小技巧:测试焦点导航时,可以打开「设置 → 无障碍 → 触摸和长按延迟」,把延迟调高一点,这样更容易看清焦点的移动路径。

测试触摸目标大小

Android官方建议,触摸目标的最小尺寸是48dp(约9mm)。为什么是48dp?因为这是人类手指的平均接触面积。

如果按钮太小,用户很难点中。特别是对于手抖的老年人,或者运动障碍的用户,小按钮简直就是噩梦。

我曾在项目中遇到过,一个删除按钮只有32dp,用户反复点不到,气得直接卸载了App。后来我们改成了48dp,投诉就没了。

检查触摸目标大小,可以用Accessibility Scanner,它会自动标记出小于48dp的控件。也可以手动计算:

// 检查按钮是否满足最小触摸目标
fun checkTouchTargetSize(view: View): Boolean {
    val width = view.width
    val height = view.height
    val minSize = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP, 48f,
        view.resources.displayMetrics
    ).toInt()
    return width >= minSize && height >= minSize
}

如果因为布局限制,按钮确实做不大,怎么办?

可以用padding来扩大触摸区域:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:minWidth="48dp"
    android:minHeight="48dp" />

这样按钮看起来可能不大,但触摸区域是够的。

测试颜色对比度

颜色对比度,说白了就是文字和背景之间的颜色差异够不够大。对比度不够,视力不好的用户就看不清。

WCAG 2.0标准要求:

类型 最小对比度 增强对比度
普通文本(小于18px) 4.5:1 7:1
大文本(大于等于18px加粗,或24px普通) 3:1 4.5:1
图标和图形 3:1 3:1

怎么计算对比度?有个公式:

对比度 = (L1 + 0.05) / (L2 + 0.05)

其中 L1 是较亮颜色的相对亮度
L2 是较暗颜色的相对亮度

当然,你不需要手动算。Accessibility Scanner会帮你算好。或者用在线工具,比如WebAIM的对比度检查器。

我建议在主题设计阶段就把对比度考虑进去。比如你的App主色调是浅蓝色,那文字就不能用白色,得用深色。否则对比度肯定不够。

核心要点:无障碍测试不是「加分项」,而是「必选项」。它关系到你的App能不能被所有人使用。从工具、内容描述、焦点导航、触摸目标、颜色对比度这五个维度入手,你就能覆盖大部分无障碍问题。

好了,这一章的内容就到这里。记住,无障碍测试不是一次性的工作,而是应该融入你的日常开发流程。每次提交代码前,花5分钟跑一遍Accessibility Scanner,你会发现很多问题在早期就被解决了。

公众号:蓝海资料掘金营,微信deep3321