为了帮助有可访问性需求的用户,Android 框架允许您创建可访问性服务,该服务可以向用户展示应用中的内容,并代表用户操作应用。
Android 提供多种系统可访问性服务,包括:
- TalkBack:帮助有视力障碍或失明的人。它通过合成语音宣布内容,并根据用户手势对应用执行操作。
- Switch Access:帮助有运动障碍的人。它突出显示交互式元素,并根据用户按下按钮执行操作。它允许仅使用一个或两个按钮来控制设备。
为了帮助有可访问性需求的人员成功使用您的应用,您的应用必须遵循本页上介绍的最佳实践,这些最佳实践建立在 使应用更易访问 中描述的指南的基础上。
以下各节中介绍的每个最佳实践都能够进一步提高应用的可访问性。
- 标记元素
- 用户必须能够理解应用中每个交互式和有意义的 UI 元素的内容和目的。
- 添加可访问性操作
- 通过添加可访问性操作,您可以让可访问性服务的用户能够完成应用中的关键用户流程。
- 扩展系统小部件
- 建立在框架包含的视图元素的基础上,而不是创建您自己的自定义视图。框架的视图和小部件类已经提供了应用所需的大多数可访问性功能。
- 使用除颜色以外的其他提示
- 用户必须能够清楚地区分 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 或 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 宣布“双击并按住以收藏”,帮助使用者理解操作的目的。
扩展系统小部件
注意:在设计您的应用的 UI 时,请使用或扩展尽可能位于 Android 类层次结构下方的系统提供的组件。位于层次结构下方的系统提供的组件已经具有您的应用所需的大多数辅助功能。扩展这些系统提供的组件比从更通用的 View
、ViewCompat
、Canvas
和 CanvasCompat
类创建自己的组件更容易。
如果您必须直接扩展 View
或 Canvas
,这对于高度自定义的体验或游戏级别可能是必要的,请参阅 使自定义视图更具可访问性。
本节以实现一种特殊类型的 Switch
(称为 TriSwitch
)为例,同时遵循扩展系统组件的最佳实践。TriSwitch
对象的工作方式类似于 Switch
对象,只是每个 TriSwitch
实例允许使用者在三种可能的状态之间切换。
从类层次结构下层扩展
Switch
对象继承了其层次结构中的多个框架 UI 类
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; } }
使用除颜色以外的其他提示
为了帮助色觉障碍的使用者,请使用颜色以外的提示来区分应用屏幕中的 UI 元素。这些技术可以包括使用不同的形状或大小、提供文本或视觉模式,或添加音频或触觉(触觉)反馈来标记元素之间的差异。
图 1 显示了活动中的两个版本。一个版本仅使用颜色来区分工作流程中的两个可能的操作。另一个版本采用了最佳实践,除了颜色之外还包括形状和文本,以突出显示两个选项之间的差异。
使媒体内容更易访问
如果您正在开发包含媒体内容(例如视频片段或音频录制)的应用,请尝试支持不同类型的辅助功能需求的使用者理解此材料。特别是,我们鼓励您执行以下操作
- 包含允许使用者暂停或停止媒体、更改音量和切换字幕(字幕)的控件。
- 如果视频呈现的信息对于完成工作流程至关重要,请以替代格式提供相同内容,例如成绩单。
其他资源
要了解有关使您的应用更具可访问性的更多信息,请参阅以下其他资源