什么是无障碍?
无障碍范围很广,一般是指在发展过程中没有阻碍,活动能够顺利进行。比如给腿脚不便的人在一些公共场合比如火车站、机场、商场等地方设置无障碍电梯,无障碍厕所,或者给听觉障碍的人提供助听器等等。
换句话说:为失能人士提供与非失能人士同等机会。这里所说的失能根据具体形式和严重程度各不相同,但主要可以分为四种:认知、视觉、听觉,以及活动能力。
当然失能也分为两种永久性失能和情境性失能。
永久性失能
视觉障碍、听觉障碍、坐轮椅或者行动不便等肢体障碍。
情境性失能
- 开车的时候试图用手机:汽车晃动导致情境性的视觉障碍、肢体障碍、注意力障碍。
- 开会时聊天软件发来语音:查看消息发出声音会影响周围同事(聊天软件提供语音转文字的功能便是无障碍的一种)。
- 出国旅游语言不通:情境性的口头沟通障碍。
- 买东西时拎着大包小包:情境性的肢体障碍。
无障碍的新定义:确保每个用户意图都被理解。
某种程度而言,信息无障碍是智能产品交互设计中针对特殊人群的一个功能,它可以让人们更加平等地享用产品在硬件和软件上的各项功能。
DinamiX 对无障碍的支持
本文接下去讲的 DinamicX 对无障碍的支持主要是信息无障碍,一般是针对视觉障碍的人群。通过技术手段,帮助视障人群更好地感受世界的美好,让用户在使用 app 的时候能够顺畅的获取信息、利用信息。
手淘基础核心链路
手淘首页、详情、购物车、下单、订单、订单列表、我的淘宝都属于手淘核心链路,目前上述页面 UI 都是使用 DinamicX 作为渲染引擎来绘制。
DinamicX SDK 作为支撑手淘基础核心链路重要的一环,对无障碍的支持责无旁贷。
关于 DinamicX
DinamicX 的定位是一个提供三端统一能力的客户端动态化解决方案,为无线基础链路上的高性能和高可用提供基础保障。我们希望通过社区化运营不断丰富 DinamicX 的能力和内容,提高渲染性能和稳定性,将 DinamicX 打造成一个集团内的客户端动态化体系的标准化方案。
动态模板解决方案核心技术:一个包含模板的下载、加载、解析、渲染的引擎,帮你动态生成 View。
DinamicX 对无障碍的支持主要分为两部分:
- SDK 本身对无障碍的跨平台支持
- 模板开发平台进行卡口校验
作为一个跨平台统一的动态化解决方案,势必要抹平端与端的差异,以及降低业务方(模板开发者)想支持无障碍的认知成本,我们团队全体成员包括 Android 开发、iOS 开发以及测试同学讨论了很久,确保两端统一的情况下,勾勒出统一无障碍行为。
技术方案
系统原生的无障碍
iOS 系统原生的无障碍
原生 iOS 的几种逻辑:
- View 设置了 isAccessibilityElement=YES,无论是否设置了 accesibilityLabel,所有它的子节点,都不可获得焦点。
- UILabel 的 isAccessibilityElement 属性默认是 NO,但只要主动地设置过值,就算设置的是 NO,也无法在父容器下自动读出。
- 如果需要父容器获得焦点后自动读取出里面所有 UILabel 的文字,需要 isAccessibilityElement = NO,并且 accessibilityElementsHidden = NO。Label 的 isAccessibilityElement 必须保持原始默认值,不能设置任何值。
- 如果父节点嵌套,并且所有父节点的 accessibilityElement 都设置为 off,会自动将这个父节点所有的子节点的 TextView 的 accessibilityLabel 顺序读出,这意味着所有自动阅读的文字最终都在根节点上被读出。
系统无障碍 API:
Android 系统原生的无障碍
Android 的 View 无障碍状态总共分为 3 种:
- 没有无障碍信息,如 ImageView、View 等默认就是没有无障碍信息。
- 有无障碍信息,如 ImageView 设置 setContentDescription,或者 TextView 自带无障碍信息就是它本身的 text。
- 有无障碍信息的可交互控件,比如 ImageView 设置 setContentDescription 的同时,又设置了 setOnClickListener, 比如 TextView 设置了setFocusable(true),或者 EditText、CheckBox 这种默认就是有无障碍信息的可交互控件。
这 3 种无障碍状态在它的父 layout 之中的关系:
系统无障碍 API:
DinamicX SDK 定义无障碍属性
抹平两端差异,简化无障碍逻辑,DinamicX 提供了两个无障碍属性来支持无障碍功能。
xml 示例
如下表示该控件在触摸到的时候,会被选中,且朗读出“跳往详情页”的文案:
<ImageView
width="100"
height="100"
accessibility="on"
accessibilityText="跳往详情页"
onTap="@openUrl{'detail'}"
imageUrl="https://img.alicdn.com/tfs/TB1FuMQQFXXXXXLXXXXXXXXXXXX-420-420.jpg"
/>
统一两端无障碍行为
下图代表的是两端目前统一行为,描述了 Layout 与子节点在无障碍属性各种 value 值碰撞下的情况。
端上的处理
为达到上图所展示的两端一致的行为,端上各自做了自己的处理。
iOS
下图表示 SDK 根据模板属性到系统 API 的映射:
Android
下图表示 SDK 根据模板属性到系统 API 的映射,Android 对 Layout 和非 Layout 的 View 需要区别对待。
Layout 节点对无障碍的处理:
非 Layout 节点对无障碍的处理:
案例演示
模板示例
<LinearLayout
backgroundColor="#eeeeee"
height="match_content"
width="375"
orientation="vertical"
disableFlatten="true"
>
<LinearLayout
marginLeft="@triple{@data{cellType},20,50}"
backgroundColor="#f2f2f2"
height="match_content"
width="match_parent"
orientation="vertical"
disableFlatten="true"
accessibility="auto"
>
<!--auto代表点击的时候,该layout下面的text信息都可以读出来-->
<TextView
width="match_content"
height="match_content"
textColor="#ff051b28"
textSize="12"
marginTop="20"
marginBottom="20"
text="这是一个textView"
/>
<TextView
width="match_content"
height="match_content"
textColor="#ff051b28"
textSize="12"
marginTop="20"
marginBottom="20"
text="这是一个有焦点的textView"
accessibility="on"
onTap="@rTap{}"
accessibilityText="这是一个有焦点的textView"
/>
<FastTextView
width="match_content"
height="match_content"
textColor="#ff051b28"
textSize="12"
marginTop="20"
marginBottom="20"
text="这是一个FastTextView"
/>
<TextView
width="match_content"
height="match_content"
textColor="#ff051b28"
textSize="12"
marginTop="20"
marginBottom="20"
accessibility="off"
text="这是一个不需要被朗读的textView"
/>
<ImageView
width="100"
height="100"
marginLeft="20"
marginTop="12"
borderWidth="3ap"
borderColor="#FF0000"
accessibility="on"
accessibilityText="这是一个ImageView点击"
onTap="@rTap{'测试'}"
imageUrl="https://img.alicdn.com/tfs/TB1FuMQQFXXXXXLXXXXXXXXXXXX-420-420.jpg"
/>
</LinearLayout>
</LinearLayout>
模板示例手机演示
看如下视频,由于 Layout accessibility 设置了 auto 属性,因此该 Layout 会被选中,并朗读内部含有无障碍信息的 Text,但是第二个和第四个 TextView 是不会朗读的,第二个配置了 onTap&accessibility=on,因此此时它属于一个可交互的控件,是需要单独被选中的,第四个 accessibility=off,因此此时它是关闭无障碍这个功能的,因此也没法选中朗读,且不会被 Layout 选中朗读。
无障碍校验卡口
支持是一方面,引导开发同学去写是另一方面。
事实上现在好多动态化的方案,包括 native 本身都会支持无障碍功能,但是这种支持是单向的,如果你只是支持,但是开发者不去支持,那最终这个产品无障碍功能依旧是缺失。
开发者为什么不去支持呢?
- 第一,无障碍的公益宣导不够,优先级不高,开发本身没有这个意识,无障碍测试用例缺失。
- 第二,无障碍功能的支持有一定的成本,且没有一套标准和规范告知什么情况下需要无障碍,且如何支持。
- 第三,流程上没有监督和管控,开发有可能会忘记。
为了更好的支持帮助视障用户使用手机淘宝,同时帮助业务方定位发现无障碍的错误,减少无障碍的测试回归工作量,我们发起了无障碍校验卡口,智能检测无障碍问题,通过调用无障碍服务来判断模板是否合格,以此确保每一个模板的发布都是支持无障碍的。
添加无障碍校验卡口这才是无障碍工作最关键的一环,目前由于手淘的核心链路都使用的 DinamicX,且 DinamicX 模板都在组件平台开发,因此只要我们加上这卡口,你想不支持无障碍都不行,否则你的动态模板发布不了。
拥有无障碍校验卡口功能的 DinamicX 开发模式流程图:
目前无障碍卡口校验的相关规则(有些校验规则也是为了抹平两端差异而加的):
- 非交互性控件,如 ImageView、FrameLayout、LinearLayout 等,若有设置 onTap 属性,则会检查是否含有无障碍属性,若没有则校验不通过,并给出建议:需要设置accessibility=on, 开启无障碍焦点,同时设置 accessibilityText= xx 属性。
- 非交互性控件,如 ImageView、FrameLayout、LinearLayout 等,若有设置 accessibility=on 的时候,必须同时设置 accessibilityText=xx。
- 子 View 设置 onTap 属性的时候,必须保证它的父 Layout 没有设置 accessibility=on,否则该子 View 是不能获取焦点的。
- 如果 Layout 设置了 auto 属性,TextView 不能只设置 onTap,还要设置 accessibility=on,否则获取不到焦点。
- accessibility 属性不能设置动态表达式。
如下视频所示,假设 Layout 节点上面设置 onTap 点击事件,那么校验卡口会提醒你该节点需要设置无障碍信息:
现阶段整个手淘首页、详情、购物车、我的淘宝、订单详情、订单列表等核心页面所开发的模板都会经过该卡口的校验。
愿景
也许我们做的不一定是最好的,但是我们会一直努力去做,不为别的,只是为了让手淘在大众心中特别是盲人的心中除了是一个购物 app 之外,更是一个有温暖的产品,一个让盲人感动的产品。
希望有一天我们的开发同学开发模板的时候,再也不需要弹起那个卡口的校验,而是写模板的那一刻,已经想起了那些拿着手机耳边听的人群。
希望有一天手淘是盲人心目中最喜欢的购物产品。
希望有一天看到这篇文章的同学们能够在心里有那么一丝触动,不是道德绑架,而是在未来某一天盲人谈起手淘那种由衷的感谢可以带给自己心灵的那种慰藉。
希望有一天看到这篇文章的同学能够感受到:无障碍是一件公益,做完心里暖暖的,技术除了有价值以外,还可以有温度。
重视无障碍,重视公益,从我们做起!
借助手机旁白功能,视障者用耳朵购物
淘宝无障碍实验室工程师闭着眼睛做无障碍测试
Git 公开课 | 10 个课时教你上手
如何在各平台下安装 Git?Git 的工作流程是怎样的?如何理解工作区、暂存区和版本库的概念?Git 有哪些基本操作?如何搭建 Git 服务器作为自己的私有仓库?识别下方二维码,或点击文末“阅读原文”来学习 Git