提升應用程式無障礙程度的基本原則

為了滿足使用者的無障礙需求,Android 架構可讓您建立無障礙服務,以便對使用者呈現應用程式內容,並代為操作應用程式。

Android 提供多種系統無障礙服務,包括以下項目:

  • TalkBack:輔助低視能或失明的使用者。透過合成語音朗讀內容,並根據使用者手勢在應用程式中執行對應動作。
  • 切換控制功能:輔助動作失能的使用者。醒目顯示互動性元素,並在使用者按下按鈕時執行對應動作,讓使用者只透過一或兩個按鈕就能控制裝置。

為了協助具有無障礙需求的使用者順利使用應用程式,應用程式必須採用本頁所述的最佳做法。這些最佳做法是以「提高應用程式的無障礙程度」一文所述的準則為基礎。

這些最佳做法可進一步提高應用程式的無障礙程度,如以下各節說明:

標籤元素
針對應用程式中每個有意義的互動性 UI 元素,使用者必須能夠瞭解其內容和用途。
新增無障礙功能動作
加入無障礙動作選項,可讓無障礙服務的使用者在應用程式中完成重要的使用者流程。
擴充系統小工具
以 Android 架構內含的檢視元素為基礎進行建構,而不要建立自訂的檢視元素。Android 架構的檢視和小工具類別已提供應用程式所需的大部分無障礙功能。
使用顏色以外的提示
使用者必須能清楚區分 UI 中不同類別的元素。因此,使用顏色區別元素時,也請使用圖案、位置來呈現差異。
提高媒體內容的無障礙程度
為應用程式的影片或音訊內容加入說明,讓存取這些內容的使用者不必完全仰賴視覺或聽覺提示。

標籤元素

針對應用程式中的每個互動式 UI 元素,請務必為使用者提供實用的描述性標籤。每個標籤都必須說明特定元素的含義和用途。TalkBack 等螢幕閱讀器可為使用者朗讀這些標籤。

在大部分情況下,您要在包含該元素的版面配置資源檔案中,指定 UI 元素的說明。您通常可以使用 contentDescription 屬性新增標籤 (如「提高應用程式的無障礙程度」指南所述)。以下各節將說明其他幾種標籤的使用技巧。

可編輯元素

為可編輯元素 (例如 EditText 物件) 加上標籤時,您可以在元素本身中提供有效輸入的範例,除了讓螢幕閱讀器讀取這個範例文字,顯示該文字也會很有幫助。在這類情況下,您可以使用 android:hint 屬性,如以下程式碼片段所示:

<!-- The hint text for en-US locale would be
     "Apartment, suite, or building". -->
<EditText
   android:id="@+id/addressLine2"
   android:hint="@string/aptSuiteBuilding" ... />

在這個情況下,View 物件的 android:labelFor 屬性必須設為 EditText 元素的 ID。詳情請參閱下節說明。

某元素描述另一元素的元素組合

EditText 元素常有一個對應的 View 物件,用於說明使用者必須在 EditText 元素中輸入的內容。您可以設定 View 物件的 android:labelFor 屬性來表示這種關係。

以下程式碼片段是為這類元素組合加上標籤的範例:


<!-- Label text for en-US locale would be "Username:" -->
<TextView
   android:id="@+id/usernameLabel" ...
   android:text="@string/username"
   android:labelFor="@+id/usernameEntry" />

<EditText
   android:id="@+id/usernameEntry" ... />

<!-- Label text for en-US locale would be "Password:" -->
<TextView
   android:id="@+id/passwordLabel" ...
   android:text="@string/password
   android:labelFor="@+id/passwordEntry" />

<EditText
   android:id="@+id/passwordEntry"
   android:inputType="textPassword" ... />

集合中的元素

為集合中的元素加上標籤時,所有標籤都不得重複。這樣一來,系統的無障礙服務在朗讀標籤時,才能僅參照一個螢幕上的元素。這種關聯可讓使用者知道自己已循環瀏覽 UI,或是已將焦點移到先前瀏覽過的元素。

請特別在重複使用的版面配置 (例如 RecyclerView 物件) 中,為所含元素加上額外文字或脈絡資訊,讓每個子項元素都不重複。

為此,請將內容說明設為轉換器實作項目,如以下程式碼片段所示:

Kotlin

data class MovieRating(val title: String, val starRating: Integer)

class MyMovieRatingsAdapter(private val myData: Array<MovieRating>):
        RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() {

    class MyRatingViewHolder(val ratingView: ImageView) :
            RecyclerView.ViewHolder(ratingView)

    override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) {
        val ratingData = myData[position]
        holder.ratingView.contentDescription = "Movie ${position}: " +
                "${ratingData.title}, ${ratingData.starRating} stars"
    }
}

Java

public class MovieRating {
    private String title;
    private int starRating;
    // ...
    public String getTitle() { return title; }
    public int getStarRating() { return starRating; }
}

public class MyMovieRatingsAdapter
        extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> {
    private MovieRating[] myData;


    public static class MyRatingViewHolder extends RecyclerView.ViewHolder {
        public ImageView ratingView;
        public MyRatingViewHolder(ImageView iv) {
            super(iv);
            ratingView = iv;
        }
    }

    @Override
    public void onBindViewHolder(MyRatingViewHolder holder, int position) {
        MovieRating ratingData = myData[position];
        holder.ratingView.setContentDescription("Movie " + position + ": " +
                ratingData.getTitle() + ", " + ratingData.getStarRating() +
                " stars")
    }
}

相關內容群組

如果應用程式會顯示多個 UI 元素,且這些元素會形成自然群組 (例如歌曲詳細資料或訊息屬性),請將這些元素設置在容器內 (通常是 ViewGroup 的子類別)。將容器物件的 android:screenReaderFocusable 屬性設為 true,並將每個內部物件的 android:focusable 屬性設為 false。這樣一來,無障礙服務就能在單次朗讀中逐一提供內部元素的內容說明。整合相關元素後,輔助技術的使用者就能更有效率地探索螢幕上的資訊。

以下程式碼片段包含彼此相關的內容片段,因此容器元素 (ConstraintLayout 的例項) 的 android:screenReaderFocusable 屬性是設為 true,各個內部 TextView 元素的 android:focusable 屬性則設為 false

<!-- In response to a single user interaction, accessibility services announce
     both the title and the artist of the song. -->
<ConstraintLayout
    android:id="@+id/song_data_container" ...
    android:screenReaderFocusable="true">

    <TextView
        android:id="@+id/song_title" ...
        android:focusable="false"
        android:text="@string/my_song_title" />
    <TextView
        android:id="@+id/song_artist"
        android:focusable="false"
        android:text="@string/my_songwriter" />
</ConstraintLayout>

由於無障礙服務會一次讀完內部元素的說明,因此每則說明務必盡量保持精簡,同時清楚傳達元素的含義。

注意:一般來說,建立群組內容說明時,應避免單純匯總子項文字。如果這麼做,群組說明會顯得生硬,而且當子項文字有所變更時,群組說明可能會與顯示的文字不相符。

在清單或格線情境下,螢幕閱讀器可能會合併清單文字,或合併格線元素的子項文字節點。建議您避免修改此朗讀內容。

巢狀群組

如果應用程式介面提供多維度資訊 (例如節慶的每日活動清單),請針對內部群組容器使用 android:screenReaderFocusable 屬性。只要使用這個標籤配置,就能在探索螢幕上內容所需的朗讀次數,以及每次朗讀的時間長度之間取得良好平衡。

下列程式碼片段是為較大群組中的群組加上標籤的一個方法:

<!-- In response to a single user interaction, accessibility services
     announce the events for a single stage only. -->
<ConstraintLayout
    android:id="@+id/festival_event_table" ... >
    <ConstraintLayout
        android:id="@+id/stage_a_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage A. -->

    </ConstraintLayout>
    <ConstraintLayout
        android:id="@+id/stage_b_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage B. -->

    </ConstraintLayout>
</ConstraintLayout>

文字中的標題

部分應用程式會使用「標題」統整螢幕上顯示的文字群組。如果特定 View 元素代表標題,您可以將該元素的 android:accessibilityHeading 屬性設為 true,為無障礙服務指出其用途。

無障礙服務的使用者可以選擇依標題 (而不是依段落或字詞) 瀏覽,透過這項彈性享有更優異的文字瀏覽體驗。

無障礙窗格標題

在 Android 9 (API 級別 28) 以上版本中,您可以為螢幕的「窗格」提供能夠滿足無障礙需求的標題。就無障礙功能來說,窗格是視窗中看起來與眾不同的部分,例如片段的內容。為了讓無障礙服務瞭解窗格的類視窗行為,請為應用程式的窗格提供描述性的標題。當窗格的外觀或內容有所改變時,無障礙服務就能為使用者提供更精細的資訊。

如要指定窗格的標題,請使用 android:accessibilityPaneTitle 屬性,如以下程式碼片段所示:

<!-- Accessibility services receive announcements about content changes
     that are scoped to either the "shopping cart view" section (top) or
     "browse items" section (bottom) -->
<MyShoppingCartView
     android:id="@+id/shoppingCartContainer"
     android:accessibilityPaneTitle="@string/shoppingCart" ... />

<MyShoppingBrowseView
     android:id="@+id/browseItemsContainer"
     android:accessibilityPaneTitle="@string/browseProducts" ... />

裝飾性元素

如果 UI 中的元素只是用於保持視覺間隔或外觀,請將其 android:importantForAccessibility 屬性設為 "no"

新增無障礙功能動作

您必須讓無障礙服務的使用者能夠輕鬆執行應用程式內的所有使用者流程。舉例來說,如果使用者可滑動清單中的某個項目,那無障礙服務也應提供這個動作,讓使用者透過其他方式完成相同的使用者流程。

讓所有操作都可提供無障礙功能

TalkBack、Voice Access 或切換控制功能的使用者可能需要透過其他方法,完成應用程式中特定的使用者流程。如果是與手勢相關的動作 (如拖曳或滑動),您可以讓應用程式以無障礙服務使用者能使用的方式,提供這類動作。

透過無障礙功能動作,應用程式可讓使用者以其他方式完成操作。

舉例來說,如果您的應用程式允許使用者滑動項目,您也可以透過自訂無障礙功能動作提供這項功能,例如:

Kotlin

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive)
) { _, _ ->
    // Same method executed when swiping on itemView
    archiveItem()
    true
}

Java

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive),
    (view, arguments) -> {
        // Same method executed when swiping on itemView
        archiveItem();
        return true;
    }
);

With the custom accessibility action implemented, users can access the action through the actions menu.

Make available actions understandable

When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."

This generic announcement doesn't give the user any context about what a touch & hold action does.

To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:

Kotlin

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
)

Java

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
);

This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.

Extend system widgets

Note: When you design your app's UI, use or extend system-provided widgets that are as far down Android's class hierarchy as possible. System-provided widgets that are far down the hierarchy already have most of the accessibility capabilities your app needs. It's easier to extend these system-provided widgets than to create your own from the more generic View, ViewCompat, Canvas, and CanvasCompat classes.

If you must extend View or Canvas directly, which might be necessary for a highly customized experience or a game level, see Make custom views more accessible.

This section uses the example of implementing a special type of Switch called TriSwitch while following best practices around extending system widgets. A TriSwitch object works similarly to a Switch object, except that each instance of TriSwitch allows the user to toggle among three possible states.

Extend from far down the class hierarchy

The Switch object inherits from several framework UI classes in its hierarchy:

View
↳ TextView
  ↳ Button
    ↳ CompoundButton
      ↳ Switch

建議您直接從 Switch 類別擴充新的 TriSwitch 類別。如此一來,Android 無障礙架構就能提供 TriSwitch 類別所需的大部分無障礙功能:

  • 無障礙功能動作:向系統告知無障礙服務如何模擬每種可能針對 TriSwitch 物件執行的使用者輸入內容 (從 View 沿用而來)。
  • 無障礙功能事件:向無障礙服務告知 TriSwitch 物件外觀在螢幕刷新或更新時各種可能的改變方式 (從 View 沿用而來)。
  • 特性:每個 TriSwitch 物件的詳細資料,例如任何所顯示文字的內容 (從 TextView 沿用而來)。
  • 狀態資訊:TriSwitch 物件目前狀態的說明,例如「已勾選」或「未勾選」(從 CompoundButton 沿用而來)。
  • 狀態的文字說明:以文字說明各個狀態所代表的意義 (從 Switch 沿用而來)。

Switch 及其父類別的行為與 TriSwitch 物件的行為幾乎相同,因此在實作時,您可以專注將可能的狀態數量從 2 個擴充到 3 個。

定義自訂事件

擴充系統小工具時,您可能會變更使用者與小工具互動方式的某個面向。請務必定義這些互動方式的變更,讓無障礙服務能夠更新應用程式的小工具,如同使用者直接與小工具互動一樣。

一般而言,每覆寫一個以檢視元素為基礎的回呼時,您也必須覆寫 ViewCompat.replaceAccessibilityAction() 來重新定義對應的無障礙動作。在應用程式測試中,您可以呼叫 ViewCompat.performAccessibilityAction() 驗證這些重新定義的動作行為。

這項原則可如何套用於 TriSwitch 物件

與一般的 Switch 物件不同,輕觸 TriSwitch 物件會循環切換三種可能的狀態,因此對應的 ACTION_CLICK 無障礙功能動作必須經過更新:

Kotlin

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}

Java

public class TriSwitch extends Switch {
    // 0, 1, or 2
    private int currentState;

    public int getCurrentState() {
        return currentState;
    }

    public TriSwitch() {
        updateAccessibilityActions();
    }

    private void updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label, (view, args) -> moveToNextState());
    }

    private void moveToNextState() {
        currentState = (currentState + 1) % 3;
    }
}

使用顏色以外的提示

為了輔助色盲使用者,請使用顏色以外的提示來區分應用程式畫面中的 UI 元素,包括運用不同的形狀或大小、提供文字或視覺圖像,或是加入語音或觸控 (觸動) 回饋來呈現不同元素的差異。

圖 1 顯示同一活動的兩個版本。其中一個版本只使用顏色來區分工作流程中的兩個可能動作。另一個版本則採用最佳做法,除了顏色以外還利用形狀和文字來凸顯兩個選項的差異:

圖 1:僅使用顏色建立 UI 元素的範例 (左),以及使用顏色、形狀和文字建立 UI 元素的範例 (右)

提高媒體內容的無障礙程度

如果您開發的應用程式包含媒體內容 (例如短片或音訊錄音),請設法為具有不同類型無障礙需求的使用者提供支援,協助他們理解這些內容。我們尤其建議您採取下列做法:

  • 加入控制項,讓使用者暫停或停止播放媒體、變更音量及切換字幕。
  • 如果影片中的資訊是完成工作流程的關鍵,請以轉錄稿等替代格式提供相同內容。

其他資源

如要進一步瞭解如何提高應用程式的無障礙程度,請參閱下列其他資源:

程式碼研究室

網誌文章