什麼是無障礙?
無障礙範圍很廣,一般是指在發展過程中沒有阻礙,活動能夠順利進行。比如給腿腳不便的人在一些公共場合比如火車站、機場、商場等地方設置無障礙電梯,無障礙廁所,或者給聽覺障礙的人提供助聽器等等。
換句話說:為失能人士提供與非失能人士同等機會。這裡所說的失能根據具體形式和嚴重程度各不相同,但主要可以分為四種:認知、視覺、聽覺,以及活動能力。
當然失能也分為兩種永久性失能和情境性失能。
永久性失能
視覺障礙、聽覺障礙、坐輪椅或者行動不便等肢體障礙。
情境性失能
- 開車的時候試圖用手機:汽車晃動導致情境性的視覺障礙、肢體障礙、注意力障礙。
- 開會時聊天軟件發來語音:查看消息發出聲音會影響周圍同事(聊天軟件提供語音轉文字的功能便是無障礙的一種)。
- 出國旅遊語言不通:情境性的口頭溝通障礙。
- 買東西時拎著大包小包:情境性的肢體障礙。
無障礙的新定義:確保每個用戶意圖都被理解。
某種程度而言,信息無障礙是智能產品交互設計中針對特殊人群的一個功能,它可以讓人們更加平等地享用產品在硬件和軟件上的各項功能。
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