为了帮助有可访问性需求的用户,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、语音访问或开关访问的用户可能需要其他方法来完成应用中的某些用户流程。对于与手势(例如拖放或滑动)相关的操作,您的应用可以以辅助功能服务用户可以访问的方式公开这些操作。
使用 辅助功能操作,应用可以为用户提供完成操作的替代方法。
例如,如果您的应用允许用户滑动项目,您还可以通过自定义辅助功能操作公开此功能,如下所示
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 之类的 辅助功能服务 将其 宣布为 "双击并按住以长按。"
此 通用 公告 没有't 向 用户 提供 任何 关于 触摸 & 按住 操作 做什么 的 上下文。
为了 使 此 公告 更具 描述性, 您可以 替换 辅助功能 操作’s 公告 如下:
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 显示了活动的两个版本。一个版本仅使用颜色来区分工作流程中两个可能的动作。另一个版本使用了最佳实践,除了颜色之外还包括形状和文本,以突出显示这两个选项之间的差异。
使媒体内容更易于访问
如果您正在开发包含媒体内容(例如视频剪辑或音频录制)的应用程序,请尝试支持不同类型的辅助功能需求的用户理解此材料。特别是,我们鼓励您执行以下操作
- 包含允许用户暂停或停止媒体、更改音量和切换字幕(隐藏式字幕)的控件。
- 如果视频呈现的信息对于完成工作流程至关重要,请以另一种格式(例如成绩单)提供相同的内容。
其他资源
要了解有关使您的应用程序更易于访问的更多信息,请参阅以下其他资源