Compose 中撰寫的應用程式應支援不同使用者的無障礙需求。無障礙服務可用來將畫面上顯示的內容,轉換成特定需求使用者所需的適當格式。為了支援無障礙服務,應用程式會透過 Android 架構中的 API 公開有關 UI 元素的語意資訊。之後,Android 架構會將該語意資訊告知無障礙服務。每項無障礙服務均可選擇向使用者說明應用程式的最佳方式。Android 提供多種無障礙服務,包括 Talkback 和 Switch Access。
語意
Compose 使用語意屬性將資訊傳遞給無障礙服務。語意屬性會提供向使用者顯示的 UI 元素相關資訊。大部分的內建元件,例如 Text
和 Button
,會將這些語意屬性填入從可組合項和子項所推測出的資訊。toggleable
和 clickable
等部分輔助鍵也會設定特定語意屬性。不過,有時架構需要更多資訊,才能向使用者說明 UI 元素。
本文件說明多種情境中,您需要在可組合項中明確加入其他資訊,方可向 Android 架構正確說明。同時也會說明如何針對特定的可組合項完全取代語意資訊。本文假設您對 Android 中的無障礙功能有基本的瞭解。
常見用途
如要協助有無障礙需求的使用者順利使用您的應用程式,建議您採用本頁所述的最佳做法。
考量最低觸控目標大小
任何使用者可點擊、輕觸或進行互動的螢幕元素,都必須設為適當大小,方便使用者進行互動。設定這些元素的大小時,請務必將大小下限設為 48dp,以便正確遵循「材質設計無障礙功能指南」。
「材質」元件,例如 Checkbox
、RadioButton
、Switch
、Slider
以及Surface
:請在內部設定此大小下限,但僅限元件可以接收使用者動作時。舉例來說,如果 Checkbox
的 onCheckedChange
參數設為非空值,系統就會在其中包含至少 48dp 的寬度和高度。
@Composable
fun CheckableCheckbox() {
Checkbox(checked = true, onCheckedChange = {})
}
當 onCheckedChange
參數設為空值時,系統不會納入邊框間距,因為該元件無法直接互動。
@Composable
fun NonClickableCheckbox() {
Checkbox(checked = true, onCheckedChange = null)
}
導入 Switch
、RadioButton
或 Checkbox
等選取控制項時,您通常會將可點擊的行為推送至父項容器,並將可組合項的點擊回呼設為 null
,然後在可組合父項中新增 toggleable
或 selectable
輔助鍵。
@Composable
fun CheckableRow() {
MaterialTheme {
var checked by remember { mutableStateOf(false) }
Row(
Modifier
.toggleable(
value = checked,
role = Role.Checkbox,
onValueChange = { checked = !checked }
)
.padding(16.dp)
.fillMaxWidth()
) {
Text("Option", Modifier.weight(1f))
Checkbox(checked = checked, onCheckedChange = null)
}
}
}
如果可點擊的可組合項大小小於最低觸控目標大小,Compose 仍會增加觸控目標大小。方法是將觸控目標大小擴大到可組合項的範圍之外。
在下列範例中,我們建立了非常小的可點擊項 Box
。觸控目標區域會自動擴大到 Box
的範圍以外,因此輕觸 Box
旁邊的按鈕仍會觸發點擊事件。
@Composable
fun DefaultPreview() {
var clicked by remember { mutableStateOf(false) }
Box(
Modifier
.size(100.dp)
.background(if (clicked) Color.DarkGray else Color.LightGray)
) {
Box(
Modifier
.align(Alignment.Center)
.clickable { clicked = !clicked }
.background(Color.Black)
.size(1.dp)
)
}
}
為避免不同可組合項的觸控區域重疊,您應盡可能針對大型可組合項使用夠大的最小尺寸。在我們的範例中,則意味著使用 sizeIn
輔助鍵來設定內部方塊的最小大小:
@Composable
fun DefaultPreview() {
var clicked by remember { mutableStateOf(false) }
Box(
Modifier
.size(100.dp)
.background(if (clicked) Color.DarkGray else Color.LightGray)
) {
Box(
Modifier
.align(Alignment.Center)
.clickable { clicked = !clicked }
.background(Color.Black)
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
)
}
}
新增點擊標籤
您可以使用點擊標籤,為可組合項的點擊行為新增語意含義。點擊標籤可說明使用者與可組合項的互動情形。無障礙服務會根據點擊標籤,向有特定需求的使用者描述應用程式。
在 clickable
修飾詞內傳遞參數,藉此設定點擊標籤:
@Composable
fun ArticleListItem(openArticle: () -> Unit) {
Row(
Modifier.clickable(
// R.string.action_read_article = "read article"
onClickLabel = stringResource(R.string.action_read_article),
onClick = openArticle
)
) {
// ..
}
}
如果您無法存取可點擊的修飾詞,則可在語意修飾詞內設定點擊標籤:
@Composable
fun LowLevelClickLabel(openArticle: () -> Boolean) {
// R.string.action_read_article = "read article"
val readArticleLabel = stringResource(R.string.action_read_article)
Canvas(
Modifier.semantics {
onClick(label = readArticleLabel, action = openArticle)
}
) {
// ..
}
}
描述視覺元素
在您定義 Image
或 Icon
可組合項時,Android 架構無法自動瞭解正在顯示的內容。您必須傳送視覺元素的文字說明。
請設想一個螢幕,可供使用者與好友分享目前的頁面。這個畫面包含可點擊的共用圖示:
單憑藉圖示,Android 架構無從得知該如何向視障使用者說明。Android 架構需要圖示的其他文字說明。
contentDescription
參數是用來描述視覺元素。您應使用本地化字串,因為系統會將這個字串提供給使用者。
@Composable
fun ShareButton(onClick: () -> Unit) {
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.label_share)
)
}
}
有些視覺元素只是單純裝飾用途,而您可能不想向使用者傳達這些元素。將 contentDescription
參數設為 null
時,您必須向 Android 架構表明這個元素沒有相關聯的動作或狀態。
@Composable
fun PostImage(post: Post, modifier: Modifier = Modifier) {
val image = post.imageThumb ?: imageResource(R.drawable.placeholder_1_1)
Image(
bitmap = image,
// Specify that this image has no semantic meaning
contentDescription = null,
modifier = modifier
.size(40.dp, 40.dp)
.clip(MaterialTheme.shapes.small)
)
}
請依此判斷特定視覺元素是否需要 contentDescription
。請自問這個元素是否傳送使用者執行工作所需的資訊。若無,最好略過該說明。
合併元素
無障礙服務 (例如 TalkBack 和切換控制功能) 可讓使用者在不同元素間移動畫面聚焦。元素只聚焦於適當的精細度很重要。如果畫面上的每個低階可組合項各自聚焦,使用者就必須互動許多次才能在畫面上移動。如果元素過度合併,問題就變成使用者可能不知道哪些元素屬於同一群組。
當您將 clickable
輔助鍵套用至可組合項時,Compose 會自動合併其中包含的所有元素。這個做法也適用於 ListItem
;系統會將清單項目中的元素合併,無障礙服務也會將其視為單一元素。
您可以將一組可組成項目組成一個邏輯群組,但該群組不可點擊,也不是清單項目的一部分。您仍希望無障礙服務將其視為單一元素。舉例來說,假設可組合項會顯示使用者的顯示圖片、使用者名稱和一些額外資訊:
您可以在 semantics
輔助鍵中使用 mergeDescendants
參數,讓 Compose 合併這些元素。這樣一來,無障礙服務只會選取已合併的元素,並合併子系的所有語意屬性。
@Composable
private fun PostMetadata(metadata: Metadata) {
// Merge elements below for accessibility purposes
Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
Image(
imageVector = Icons.Filled.AccountCircle,
contentDescription = null // decorative
)
Column {
Text(metadata.author.name)
Text("${metadata.date} • ${metadata.readTimeMinutes} min read")
}
}
}
無障礙服務現在一次聚焦於整個容器,並合併其內容:
新增自訂動作
請查看下列清單項目:
使用 TalkBack 等螢幕閱讀器聆聽螢幕上顯示的內容時,會先選取整個項目,然後選取書籤圖示。
在冗長的清單,重複性可能變得很高。更好的做法是定義自訂動作,讓使用者將項目加入書籤。提醒您,您也必須明確移除書籤圖示本身的行為,以確保無障礙服務不會選取書籤圖示本身。
請使用 clearAndSetSemantics
輔助鍵來完成這項操作:
@Composable
fun PostCardSimple(
/* ... */
isFavorite: Boolean,
onToggleFavorite: () -> Boolean
) {
val actionLabel = stringResource(
if (isFavorite) R.string.unfavorite else R.string.favorite
)
Row(modifier = Modifier
.clickable(onClick = { /* ... */ })
.semantics {
// Set any explicit semantic properties
customActions = listOf(
CustomAccessibilityAction(actionLabel, onToggleFavorite)
)
}
) {
/* ... */
BookmarkButton(
isBookmarked = isFavorite,
onClick = onToggleFavorite,
// Clear any semantics properties set on this node
modifier = Modifier.clearAndSetSemantics { }
)
}
}
說明元素的狀態
元件可定義語意的 stateDescription
,供 Android 架構用來解讀可組合項所處的狀態。舉例來說,可切換的可組合項可能處於「已勾選」或「未勾選」狀態。在部分情況下,您可能會想覆寫 Compose 使用的預設狀態說明標籤。您可以先明確指定狀態說明標籤,再將可組合項定義為可切換:
@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
val stateSubscribed = stringResource(R.string.subscribed)
val stateNotSubscribed = stringResource(R.string.not_subscribed)
Row(
modifier = Modifier
.semantics {
// Set any explicit semantic properties
stateDescription = if(selected) stateSubscribed else stateNotSubscribed
}
.toggleable(
value = selected,
onValueChange = { onToggle() }
)
) {
/* ... */
}
}
定義標題
應用程式有時會在同一個畫面中,使用可捲動的容器顯示大量內容。舉例來說,畫面可能會顯示使用者正在閱讀文章的完整內容:
無障礙功能的使用者無法順利瀏覽這類畫面。 如要協助他們順利瀏覽,你可以指出哪些元素是標題。在上例中,每個子區段標題都可設定為無障礙標題。某些無障礙服務 (例如 Talkback) 可讓使用者從標題直接瀏覽到另一個標題。
在 Compose 中,您可以透過定義語意屬性的方式,指出可組合項為標題:
@Composable
private fun Subsection(text: String) {
Text(
text = text,
style = MaterialTheme.typography.h5,
modifier = Modifier.semantics { heading() }
)
}
建立自訂低階可組合項
更進階的用途是將應用程式中的特定「材質」元件替換成自訂版本。在這種情況下,請務必考量無障礙功能。假設您將「材質」Checkbox
替換成自己的實作項目,使用者很容易忘記加入負責處理此元件無障礙屬性的 triStateToggleable
修飾符。
原則上,您應在「材質」程式庫中查看該元件的實作方式,並模仿能找到的任何無障礙功能。 此外,請多多利用基礎修飾符 (而非 UI 層級的修飾符),因為前者內建便有考慮無障礙功能。請務必透過多項無障礙服務測試自訂元件的實作,驗證其行為。
瞭解詳情
如要進一步瞭解在 Compose 程式碼中支援無障礙功能,請參閱 Jetpack Compose 程式碼研究室的無障礙功能。