API 預設值

Material、Compose UI 和 Foundation API 預設會實作並提供許多無障礙做法。這些元件包含內建語意,會遵循其特定角色和功能。這表示您幾乎不需要額外作業,就能提供大部分的無障礙支援。

只要針對適當用途使用適當的 API,元件通常會隨附預先定義的無障礙行為,涵蓋標準用途。不過,請務必仔細檢查這些預設值是否符合您的無障礙需求。如果沒有,Compose 也提供其他方法,可滿足更具體的需求。

瞭解 Compose API 的預設無障礙語意和模式,有助於您在使用時考量無障礙功能。此外,您也可以在更多自訂元件中支援無障礙功能。

觸控目標大小下限

任何使用者可點擊、輕觸或進行互動的螢幕元素,都必須設為適當大小,方便使用者進行互動。設定這些元素的大小時,請務必將大小下限設為 48dp,以便正確遵循「Material Design 無障礙指南」。

Material 元件 (例如 Checkbox、RadioButton、Switch、Slider 和 Surface) 會在內部設定這個大小下限,但僅限元件可以接收使用者動作時。舉例來說,如果 Checkbox 的 onCheckedChange 參數設為非空值,核取方塊就會包含至少 48 dp 的寬度和高度。

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

核取方塊,預設邊框間距的寬度和高度為 48 dp。
圖 1. 預設邊框間距的核取方塊。

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

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

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

導入 Switch、RadioButton 或 Checkbox 等選取控制項時,您通常會將可點擊的行為推送至父項容器,方法是將可組合項的點擊回呼設為 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)
        }
    }
}

「選項」文字旁的核取方塊,正在選取和取消選取。
圖 3. 可點擊的核取方塊。

如果可點擊的可組合項大小小於最低觸控目標大小,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)
        )
    }
}

輕觸方塊旁邊,即可將非常小的可點擊方塊展開為較大的觸控目標。
圖 4. 一個非常小的可點擊方塊,展開後會變成較大的觸控目標。

為避免不同可組合項的觸控區域重疊,請一律針對可組合項使用夠大的最小尺寸。在範例中,這代表使用 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)
        )
    }
}

前一個範例中的極小方塊會增加大小,以建立較大的觸控目標。
圖 5. 較大的方塊觸控目標。

如果元素必須保持較小的可見大小,請改用 Material 3 的 minimumInteractiveComponentSize 修飾符。它會在每個維度中保留至少 48 dp 的版面配置空間,並將元素置於該空間的中央,因此元素會保持其繪製大小,而周圍的元素會移動以騰出空間。修飾符不會展開觸控目標本身,而是在版面配置中預留空間,以便進行上述展開作業。Checkbox 等 Material 元件會在內部使用這個修飾符。請將其放在任何大小修飾符之前,因為在它之前的大小修飾符會限制它可以保留的空間。

@Composable
private fun MinimumInteractiveBox() {
    var clicked by remember { mutableStateOf(false) }
    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            Modifier
                .clickable { clicked = !clicked }
                .minimumInteractiveComponentSize()
                .size(24.dp)
                .background(if (clicked) Color.DarkGray else Color.Black)
        )
        Text("Option")
    }
}

圖像元素

定義 Image 或 Icon 可組合項時,Android 架構無法自動瞭解應用程式顯示的內容。您必須傳送圖形元素的文字說明。

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

四個可點擊的圖示列,其中醒目顯示「共用」圖示。
圖 6. 一列可點擊的圖示,其中已選取「分享」圖示。

單憑圖示,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 主要用於圖片等圖形元素。Material 元件 (例如 Button 或 Text) 和可執行的行為 (例如 clickable 或 toggleable) 具有其他預先定義的語意,用於描述其內在行為,並可透過其他 Compose API 變更。

互動式元素

Material 和 Foundation Compose API 會建立使用者可透過 clickable 和 toggleable 修飾符 API 互動的 UI 元素。由於可互動元件可能包含多個元素,clickable 和 toggleable 預設會合併子項的語意,因此元件會視為一個邏輯實體。

舉例來說,Material Button 可能包含子項圖示和一些文字。Material Button 預設會合併子項語意,而非將子項視為個別項目,因此無障礙服務可以據此將子項分組:

按鈕的子項語意未合併與合併的對比。
圖 7. 按鈕的子項語意未合併與合併的比較。

同樣地,使用 clickable 修飾符也會導致可組合項將其子系的語意合併為單一實體,並傳送至無障礙服務,並附上對應的動作表示法:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

您也可以在父項可點擊項目上設定特定 onClickLabel,為無障礙服務提供額外資訊,並更精確地呈現動作:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

以 TalkBack 為例,這個 clickable 修飾符及其點擊標籤可讓 TalkBack 提供「輕觸兩下即可開啟這篇文章」的操作提示,而非更通用的預設回饋「輕觸兩下即可啟用」。

這項回饋會因動作類型而異。長按會提供 TalkBack 提示「輕觸兩下並按住」,後面接著標籤:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

在某些情況下,您可能無法直接存取 clickable 修飾符 (例如,當它設定在較低的巢狀層中),但仍想變更預設的公告標籤。如要這麼做,請使用 semantics 修飾符,將設定 clickable 與修改公告分開,並在該處設定點擊標籤,以修改動作表示法:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

您不需要傳遞點擊動作兩次。現有的 Compose API (例如 clickable 或 Button) 會為您處理這項作業。合併邏輯會驗證是否針對現有資訊採用最外層的修飾符標籤和動作。在上述範例中,NestedArticleListItem 會自動將 openArticle() 點擊動作傳遞至其 clickable 語意。您可以在第二個語意修飾符動作中,將點擊動作留空。不過,點擊標籤會取自第二個語意修飾符 onClick(label = "Open this document"),因為第一個修飾符中沒有這個標籤。

您可能會遇到預期子項語意會合併至父項語意,但並未發生的情況。詳情請參閱「合併和清除」。

自訂元件

建構自訂元件時,請查看 Material 程式庫或其他 Compose 程式庫中類似元件的實作方式。然後視情況模擬或修改其無障礙行為。舉例來說,如果您將 Material Checkbox 替換成自己的實作項目,查看現有的 Checkbox 實作項目時,系統會提醒您新增 triStateToggleable 修飾符,用於處理元件的無障礙屬性。此外,請多多利用基礎修飾符,因為這類修飾符內建無障礙功能考量,以及本節涵蓋的現有 Compose 做法。

您也可以在「清除及設定語意」一節中找到自訂切換元件的範例,並在 API 指南中進一步瞭解如何在自訂元件中支援無障礙功能。