为帮助有无障碍功能需求的用户,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 类层次结构中尽可能深层的系统提供的微件。层次结构中深层的系统提供的微件已具备您的应用所需的大部分无障碍功能。扩展这些系统提供的微件要比从更通用的View
、ViewCompat
、Canvas
和 CanvasCompat
类创建自己的微件更容易。
如果您必须直接扩展 View
或 Canvas
(这对于高度定制的体验或游戏级别可能是必要的),请参阅让自定义视图更具无障碍性。
本部分以实现一种名为 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)的两个版本。其中一个版本仅使用颜色来区分工作流程中的两个可能操作。另一个版本则采用最佳实践,除了颜色之外,还包括形状和文本来突出显示两个选项之间的差异。
让媒体内容更具无障碍性
如果您正在开发包含媒体内容(例如视频剪辑或音频录音)的应用,请尝试支持有不同无障碍功能需求的用户理解此材料。我们特别鼓励您执行以下操作:
- 包含允许用户暂停或停止媒体、更改音量和切换字幕的控件。
- 如果视频呈现的信息对于完成工作流程至关重要,请以其他格式提供相同内容,例如文字稿。
更多资源
要了解更多关于让应用更具无障碍性的信息,请参阅以下其他资源: