提高应用无障碍功能的原则

为帮助有无障碍功能需求的用户,Android 框架允许您创建无障碍服务,该服务可向用户呈现应用中的内容,也可代表用户操作应用。

Android 提供多项系统无障碍服务,包括以下内容:

  • TalkBack:可帮助低视力或失明用户。它通过合成语音播报内容,并根据用户手势在应用中执行操作。
  • Switch Access:可帮助运动障碍人士。它会突出显示交互式元素,并根据用户按下按钮执行操作。它允许用户仅使用一两个按钮来控制设备。

为了帮助有无障碍功能需求的用户成功使用您的应用,您的应用必须遵循本页面中所述的最佳实践,这些最佳实践基于让应用更具无障碍性中描述的准则。

以下各部分中描述的每项最佳实践都可以进一步提高您应用的无障碍功能:

为元素添加标签
用户必须能够理解应用中每个交互式且有意义的界面元素的内容和用途。
添加无障碍操作
通过添加无障碍操作,您可以让无障碍服务用户在您的应用中完成重要的用户流程。
扩展系统微件
在框架中包含的视图元素基础上进行构建,而不是创建自己的自定义视图。框架的视图和微件类已提供您应用所需的大部分无障碍功能。
使用颜色以外的提示
用户必须能够清楚区分界面中不同类别的元素。为此,除了颜色之外,还可以使用图案和位置来表达这些差异。
让媒体内容更具无障碍性
为您的应用视频或音频内容添加描述,以便消费这些内容的用户不必完全依赖视觉或听觉提示。

为元素添加标签

为应用中的每个交互式界面元素提供有用且具描述性的标签非常重要。每个标签都必须解释特定元素的含义和用途。TalkBack 等屏幕阅读器可以向用户播报这些标签。

在大多数情况下,您可以在包含界面元素的布局资源文件中指定该元素的描述。通常,您可以使用 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" ... />

集合中的元素

在为集合中的元素添加标签时,每个标签都必须是唯一的。这样,系统的无障碍服务在播报标签时就能准确地指向一个屏幕元素。这种对应关系让用户在循环浏览界面或将焦点移到已发现的元素时知道当前所选是哪个元素。

特别是,在可重用布局(例如 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")
    }
}

相关内容分组

如果您的应用显示形成自然分组的多个界面元素(例如歌曲详情或消息属性),请将这些元素安排在容器中,该容器通常是 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>

文本中的标题

有些应用使用 headings 来总结屏幕上出现的文本组。如果某个特定的 View 元素表示标题,您可以通过将其 android:accessibilityHeading 属性设置为 true 来向无障碍服务表明其用途。

无障碍服务用户可以选择在标题之间导航,而不是在段落或单词之间导航。这种灵活性可改善文本导航体验。

无障碍窗格标题

在 Android 9 (API level 28) 及更高版本中,您可以为屏幕的 panes 提供无障碍标题。出于无障碍功能的目的,窗格是窗口中视觉上独立的部分,例如片段的内容。为了让无障碍服务了解窗格的类似窗口行为,请为应用的窗格提供描述性标题。当窗格的外观或内容发生变化时,无障碍服务可以向用户提供更精细的信息。

要指定窗格的标题,请使用 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" ... />

装饰性元素

如果界面中的某个元素仅用于视觉间距或视觉外观目的,请将其 android:importantForAccessibility 属性设置为 "no"

添加无障碍操作

让无障碍服务用户能够轻松执行应用中的所有用户流程非常重要。例如,如果用户可以在列表中的某个项目上滑动,此操作也可以暴露给无障碍服务,以便用户有另一种方式来完成相同的用户流程。

使所有操作都可无障碍访问

TalkBack、语音访问或 Switch 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;
    }
);

实现自定义无障碍操作后,用户可以通过操作菜单访问该操作。

让可用的操作易于理解

当视图支持长按等操作时,TalkBack 等无障碍服务会将其播报为“双击并长按可长按”。

这种通用播报并未向用户提供长按操作作用的任何上下文信息。

为使此播报更具描述性,您可以替换无障碍操作的播报,如下所示:

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
);

这会导致 TalkBack 播报“双击并长按以收藏”,从而帮助用户理解此操作的目的。

扩展系统微件

注意:在设计您应用的界面时,请使用或扩展 Android 类层次结构中尽可能深层的系统提供的微件。层次结构中深层的系统提供的微件已具备您的应用所需的大部分无障碍功能。扩展这些系统提供的微件要比从更通用的ViewViewCompatCanvasCanvasCompat 类创建自己的微件更容易。

如果您必须直接扩展 ViewCanvas(这对于高度定制的体验或游戏级别可能是必要的),请参阅让自定义视图更具无障碍性

本部分以实现一种名为 TriSwitch 的特殊 Switch 类型为例,同时遵循扩展系统微件的最佳实践。TriSwitch 对象的工作方式与 Switch 对象类似,不同之处在于 TriSwitch 的每个实例都允许用户在三种可能状态之间切换。

从类层次结构中尽可能深的位置进行扩展

Switch 对象从其层次结构中的多个框架界面类继承而来:

View
 TextView
   Button
     CompoundButton
       Switch

新的 TriSwitch 类最好直接从 Switch 类扩展。这样,Android 无障碍功能框架就能提供 TriSwitch 类所需的大部分无障碍功能:

  • 无障碍操作:为系统提供信息,说明无障碍服务如何模拟在 TriSwitch 对象上执行的每种可能的用户输入。(继承自 View。)
  • 无障碍事件:为无障碍服务提供信息,说明当屏幕刷新或更新时,TriSwitch 对象的外观可能以何种方式发生变化。(继承自 View。)
  • 特性:关于每个 TriSwitch 对象的详细信息,例如它显示的任何文本内容。(继承自 TextView。)
  • 状态信息:描述 TriSwitch 对象的当前状态,例如“已选中”或“未选中”。(继承自 CompoundButton。)
  • 状态的文本描述:对每种状态所代表内容的基于文本的解释。(继承自 Switch。)

Switch 及其超类的这种行为与 TriSwitch 对象的行为几乎相同。因此,您的实现可以专注于将可能的状态数量从两个扩展到三个。

定义自定义事件

当您扩展系统微件时,您可能会更改用户与该微件交互的方式。定义这些交互更改非常重要,以便无障碍服务可以像用户直接与微件交互一样更新您的应用微件。

一个通用准则是,对于您覆盖的每个基于视图的回调,您还需要通过覆盖 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;
    }
}

使用颜色以外的提示

为帮助色觉障碍用户,请使用颜色以外的提示来区分应用屏幕中的界面元素。这些技术可以包括使用不同的形状或大小,提供文本或视觉图案,或添加基于音频或触觉(触感)的反馈来标记元素的差异。

图 1 显示了一个活动(Activity)的两个版本。其中一个版本仅使用颜色来区分工作流程中的两个可能操作。另一个版本则采用最佳实践,除了颜色之外,还包括形状和文本来突出显示两个选项之间的差异。

图 1. 仅使用颜色(左)和使用颜色、形状和文本(右)创建界面元素的示例。

让媒体内容更具无障碍性

如果您正在开发包含媒体内容(例如视频剪辑或音频录音)的应用,请尝试支持有不同无障碍功能需求的用户理解此材料。我们特别鼓励您执行以下操作:

  • 包含允许用户暂停或停止媒体、更改音量和切换字幕的控件。
  • 如果视频呈现的信息对于完成工作流程至关重要,请以其他格式提供相同内容,例如文字稿。

更多资源

要了解更多关于让应用更具无障碍性的信息,请参阅以下其他资源:

Codelabs

博文