改进应用无障碍功能要遵循的原则

为了帮助用户满足无障碍功能需求,Android 框架支持 创建一项无障碍服务,以便向用户显示应用中的内容 还能代表客户运营应用

Android 提供了一些系统无障碍服务,其中包括:

  • TalkBack: 可帮助弱视或失明的人。它通过 合成语音,并在应用中执行操作以响应用户手势。
  • 开关控制: 可以帮助有运动障碍的人士。它突出显示了互动元素 并执行操作来响应用户按下按钮的操作。它允许 只使用一个或两个按钮来控制设备。

为了帮助有无障碍需求的用户成功使用您的应用,您的 应用必须遵循本页介绍的最佳做法, 请参阅让 Google Play 上的应用 易于使用

这些最佳实践(如以下部分所述) 可以进一步改进应用的无障碍功能:

标签元素
用户必须能够理解每次互动的内容和目的 和有意义的界面元素
添加无障碍操作
通过添加无障碍操作,您可以让使用无障碍设施的用户 帮助您完成应用内的关键用户流。
扩展系统 widget
基于框架包含的视图元素构建,而不是构建 创建您自己的自定义视图框架的视图和微件类已经提供了应用所需的大多数无障碍功能。
使用除颜色之外的提示
用户必须能清楚地区分 一个界面为此,除了颜色之外,还应使用图案和位置表示这些差异。
让媒体内容使用起来更没有障碍
为应用的视频或音频内容添加说明,以便用户 观看此类内容的用户无需完全依赖视觉或听觉提示。

标签元素

请务必针对每个产品 交互式界面元素每个标签都必须说明其含义 特定元素的用途。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:" --
>T<extView
   android:id="@+id/usernameLabel" ...
   android:text="@string/username"
   android:labelFor="@+id/usernameEntry" /
>
E<ditText
   android:id="@+id/usernameEntry" ... /
>
!<-- Label text for en-US locale would be "Password:" --
>T<extView
   android:id="@+id/passwordLabel" ...
   android:text="@string/password
   android:labelFor="@+id/passwordEntry" /
>
E<ditText
   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>

文字中的标题

某些应用使用标题总结屏幕上显示的多组文字。如果特定的 View 元素表示一个标题,您可以通过将该元素的 android:accessibilityHeading 属性设为 true,表明它的无障碍服务用途。

无障碍服务的用户可以选择浏览标题,而不是浏览段落或字词。这种灵活性可改善文字浏览体验。

无障碍窗格标题

在 Android 9(API 级别 28)及更高版本中,您可以为屏幕的窗格提供使用起来没有障碍的标题。出于无障碍目的,窗格是窗口中能够从视觉上加以区分的部分,如 Fragment 的内容。为了让无障碍服务了解 窗格的类似窗口行为,为应用的 窗格。这样一来,当窗格的外观或内容发生变化时,无障碍服务就可以为用户提供更精细的信息。

如需指定窗格的标题,请使用 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、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 actions 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

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

  • 无障碍操作:向系统提供关于无障碍如何 服务可以模拟在 TriSwitch 上执行的各种可能的用户输入。 对象。(继承自 View。)
  • 无障碍事件:无障碍服务的 TriSwitch 对象的外观可能在屏幕显示时改变 刷新或更新(继承自 View。)
  • 特征:关于每个 TriSwitch 对象的详细信息,例如 显示任何文本的内容。(继承自 TextView。)
  • 状态信息TriSwitch 对象当前状态的说明, 例如“已选中”或“未选中”(继承自 CompoundButton。)
  • 状态的文字说明:对每种状态的纯文字说明 代表什么。(继承自 Switch。)

Switch 及其父类的这种行为几乎是 与 TriSwitch 对象的行为相同。因此,您的实现方法 侧重于将可能的状态数量从两种增加到三种。

定义自定义事件

当您扩展某个系统微件时,可能会改变用户与该微件互动方式的某一方面。务必要定义这些互动变化 以便无障碍服务可以更新应用的 widget 与 widget 直接交互。

一般准则是,对于您替换的每个基于视图的回调, 您还需要通过替换 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. 仅使用颜色创建界面元素的示例 (左)使用颜色、形状和文本(右)。

让媒体内容使用起来更没有障碍

如果您开发的应用包含视频剪辑或音频录音等媒体内容,应设法为具有不同类型的无障碍功能需求的用户提供支持,让他们能够理解此类内容。特别是,我们建议您做到以下几点:

  • 添加可让用户暂停或停止播放媒体、调整音量以及切换字幕的控件。
  • 如果视频提供的信息对完成某个工作流程至关重要, 以其他格式(例如转写文稿)提供相同的内容。

其他资源

如需详细了解如何让您的应用使用起来更没有障碍,请参阅下面列出的其他资源:

Codelab

博文