改善 Compose 無障礙功能的重要步驟

如要協助有無障礙需求的使用者順利使用您的應用程式,請設計應用程式的 可支援重要的無障礙需求

考量最低觸控目標大小

任何使用者可點擊、輕觸或互動的螢幕元素,都必須設為 大小足以產生可靠的互動設定這些元素大小時,請務必 將最小尺寸設為 48dp,以便正確遵循 Material Design 無障礙設計指南

Material 元件,例如 CheckboxRadioButtonSwitch SliderSurface:在內部設定這個最小尺寸,但僅限 元件接收使用者動作的時機舉例來說,如果 Checkbox 含有 如果 onCheckedChange 參數設為非空值,則核取方塊包括 邊框間距,寬度和高度至少為 48 dp。

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

onCheckedChange 參數設為空值時,邊框間距不會 ,因為該元件無法直接互動。

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

圖 1.沒有邊框間距的核取方塊。

實作 SwitchRadioButtonCheckbox,一般會將可點擊行為移至父項容器,也就是 在對 null 的可組合函式上按一下回呼,並新增 toggleable 或 父項可組合函式的 selectable 修飾符。

@Composable
private 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
private fun SmallBox() {
    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
private fun LargeBox() {
    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
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

此外,如果沒有可點擊修飾符的存取權,請將 語意修飾符中的點擊標籤:

@Composable
private 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)
        }
    ) {
        // ..
    }
}

描述視覺元素

定義 ImageIcon 可組合函式時,沒有 讓 Android 架構能夠自動瞭解應用程式 。您必須傳送視覺元素的文字說明。

請設想一個螢幕,可供使用者與好友分享目前的頁面。這個 畫麵包含可點擊的分享圖示:

可點擊的圖示列,

單憑圖示,Android 架構就無法以視覺元素描述該架構 。Android 架構需要額外的文字說明 圖示。

contentDescription 參數說明視覺元素。使用本地化翻譯 字串,因為使用者可看到的文字。

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

有些視覺元素只是單純裝飾用途,您可能不想表達 提供給使用者將 contentDescription 參數設為 null 時, 表示 Android 架構,表示此元素沒有關聯 動作或狀態

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = 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;清單項目內的元素會合併在一起,而無障礙功能 這些容器視為一個元素

可以有一組可組合函式形成邏輯群組,但 群組不可點擊,或不屬於清單項目的一部分。你仍需要無障礙服務 即可將這些元素視為單一元素例如,假設某個可組合元件 會顯示使用者的顯示圖片、名稱以及一些額外資訊:

一組 UI 元素,包括使用者名稱。已選取名稱。

您可以使用 mergeDescendants,讓 Compose 合併這些元素 semantics 修飾符中的參數值。如此一來,無障礙服務 請只選取合併的元素,以及子系的所有語意屬性 已合併。

@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")
        }
    }
}

無障礙服務現在一次聚焦於整個容器,合併 他們的內容:

一組 UI 元素,包括使用者名稱。所有元素一併選取。

新增自訂動作

請查看下列清單項目:

一般清單項目,包含文章標題、作者和書籤圖示。

使用 TalkBack 等螢幕閱讀器聽取內容 會先選取整個項目,再選取書籤圖示。

清單項目,已一併選取所有元素。

清單項目,僅選取書籤圖示

在冗長的清單,重複性可能變得很高。更好的方法是 定義可讓使用者將商品加入書籤的自訂動作。注意事項 您也必須明確移除書籤圖示 以確定無障礙服務並未選取該類別。這個 方法是使用 clearAndSetSemantics 修飾符:

@Composable
private 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 中,您可以透過定義可組合項目定義可組合項為「標題」 semantics 屬性:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

處理自訂可組合函式

每次將應用程式中的特定 Material 元件替換為自訂元件 就必須留意無障礙設計

假設您要將 Material Checkbox 替換為自己的實作項目。 您可能會忘記新增會控制代碼的 triStateToggleable 修飾符 此元件的無障礙功能屬性

原則上,我們來看看元件的實作 來模擬 Material 程式庫中可找到的任何無障礙功能行為。 此外,請大量使用基礎修飾符,而非 UI 層級 修飾符,因為這包括內建無障礙功能的考量重點。

利用多個 驗證其行為。

其他資源