什么是无障碍?
无障碍范围很广,一般是指在发展过程中没有阻碍,活动能够顺利进行。比如给腿脚不便的人在一些公共场合比如火车站、机场、商场等地方设置无障碍电梯,无障碍厕所,或者给听觉障碍的人提供助听器等等。
换句话说:为失能人士提供与非失能人士同等机会。这里所说的失能根据具体形式和严重程度各不相同,但主要可以分为四种:认知、视觉、听觉,以及活动能力。
当然失能也分为两种永久性失能和情境性失能。
永久性失能:
- 视觉障碍、听觉障碍、坐轮椅或者行动不便等肢体障碍
情境性失能:
- 开车的时候试图用手机:汽车晃动导致情境性的视觉障碍、肢体障碍、注意力障碍
- 开会时聊天软件发来语音:查看消息发出声音会影响周围同事(好的聊天软件提供语音转文字的功能便是无障碍的一种)
- 出国旅游语言不通:情境性的口头沟通障碍
- 买东西时拎着大包小包:情境性的肢体障碍
无障碍的新定义:确保每个用户意图都被理解
某种程度而言,信息无障碍是智能产品交互设计中针对特殊人群的一个功能,它可以让人们更加平等地享用产品在硬件和软件上的各项功能。
阿里无障碍的历程
DinamicX对无障碍的支持
▐ 目标与定位
通过技术手段,帮助视障人群更好地感受世界的美好,让用户在使用app的时候能够顺畅的获取信息、利用信息。
▐ DinamicX
DinamicX SDK作为支撑手淘基础核心链路重要的一环,首当其冲肩负起支持无障碍的功能。
手淘基础核心链路?手淘首页、详情、购物车、下单、订单、订单列表、我的淘宝都属于手淘核心链路,目前上述页面UI都是使用DinamicX作为渲染引擎来绘制。
什么是DinamicX?DinamicX的定位是一个提供三端统一能力的客户端动态化解决方案,为无线基础链路上的高性能和高可用提供基础保障。我们希望通过社区化运营不断丰富DinamicX的能力和内容,提高渲染性能和稳定性,将DinamicX打造成一个集团内的客户端动态化体系的标准化方案。
动态模板解决方案核心技术:一个包含模板的下载、加载、解析、渲染的引擎,帮你动态生成View。
讲完DinamicX,接下来我们讲DinamicX对无障碍的支持。
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之外,更是一个温暖的产品,一个让盲人感动的产品。
希望有一天我们的开发同学开发模板的时候,再也不需要弹起那个卡口的校验,而是写模板的那一刻,已经想起了那些拿着手机耳边听的人群。
希望有一天手淘是盲人心目中最喜欢的购物产品。
希望有一天看到这篇文章的同学们能够在心里有那么一丝触动,不是道德绑架,而是在未来某一天盲人谈起手淘那种由衷的感谢可以带给自己心灵的那种慰藉。
希望有一天看到这篇文章的同学能够感受到:无障碍是一件公益,做完心里暖暖的,技术除了有价值以外,还可以有温度的。
重视无障碍 重视公益 从我们做起!
借助手机旁白功能,视障者用耳朵购物
淘宝无障碍实验室工程师闭着眼睛做无障碍测试
本篇内容来自阿里巴巴淘系技术部大饱。