控制遍歷順序

根據預設,系統會在 Compose 應用程式中實作無障礙螢幕閱讀器行為 原本的閱讀順序,通常從左到右,再從上到底部。 但某些類型的應用程式版面配置,演算法無法判斷這類版面配置 讀取順序,而不額外提示在以觀看為基礎的應用程式中,你可以: 如要修正這類問題,請使用 traversalBeforetraversalAfter 屬性。 從 Compose 1.5 開始,Compose 提供同樣彈性的 API,但 新的概念模型

isTraversalGrouptraversalIndex 是語意屬性, 可讓您控制無障礙和 TalkBack 焦點順序, 不適用預設排序演算法isTraversalGroup 識別 語意的重要群組,而 traversalIndex 則會調整 個別元素你可以單獨使用「isTraversalGroup」。 或使用 traversalIndex,進一步自訂內容。

在以下應用程式中使用isTraversalGrouptraversalIndex: 應用程式,來控制螢幕閱讀器的周遊順序。

使用 isTraversalGroup 將元素分組

isTraversalGroup 是一個布林值屬性,用於定義是否提供語意 節點為週遊群組這類節點會為其函式提供服務 做為組織節點子項的邊界或邊界。

在節點上設定 isTraversalGroup = true,代表該節點的所有子項 才會在移動到其他元素前 先造訪過其他元素您可以將isTraversalGroup設為 非螢幕閱讀器可聚焦節點,例如 Column、Rows 或 Boxes。

以下範例使用 isTraversalGroup。它會發出四個文字元素。 左側兩個元素屬於一個 CardBox 元素,而右側的兩個元素 屬於另一個 CardBox 元素:

// CardBox() function takes in top and bottom sample text.
@Composable
fun CardBox(
    topSampleText: String,
    bottomSampleText: String,
    modifier: Modifier = Modifier
) {
    Box(modifier) {
        Column {
            Text(topSampleText)
            Text(bottomSampleText)
        }
    }
}

@Composable
fun TraversalGroupDemo() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is "
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
            topSampleText1,
            bottomSampleText1
        )
        CardBox(
            topSampleText2,
            bottomSampleText2
        )
    }
}

程式碼會產生類似以下的輸出內容:

具有兩欄文字的版面配置,左欄顯示「This」
  語句在左欄中而右欄顯示「這個句子在右邊」
圖 1. 包含兩個句子的版面配置 (左側為一個) 一欄)。

由於未設定語意,因此螢幕閱讀器預設行為為 從左到右,由上而下掃遍元素因此 預設情況下,TalkBack 會以錯誤的順序讀出語句片段:

「這個句子是」→「這個句子是」→ 「左欄。」→ " 。」

為確保片段正確排序,請修改原始程式碼片段以設定 isTraversalGrouptrue

@Composable
fun TraversalGroupDemo2() {
    val topSampleText1 = "This sentence is in "
    val bottomSampleText1 = "the left column."
    val topSampleText2 = "This sentence is"
    val bottomSampleText2 = "on the right."
    Row {
        CardBox(
//      1,
            topSampleText1,
            bottomSampleText1,
            Modifier.semantics { isTraversalGroup = true }
        )
        CardBox(
//      2,
            topSampleText2,
            bottomSampleText2,
            Modifier.semantics { isTraversalGroup = true }
        )
    }
}

因為 isTraversalGroup 已明確為每個 CardBox 上設定,所以 CardBox 排序元素時會套用邊界。在這個範例中, 會先朗讀 CardBox,後面接著正確的 CardBox

現在,TalkBack 會以正確順序朗讀語句片段:

「這個句子是」→ 「左欄。」→「這個句子是」→ " 。」

進一步自訂週遊順序

traversalIndex 是浮動屬性,可讓您自訂 TalkBack 遍歷順序。如果將多個元素組合在一起是 TalkBack 的功能 可正常運作,請將 traversalIndexisTraversalGroup,可進一步自訂螢幕閱讀器的排序方式。

traversalIndex 屬性具備下列特性:

  • traversalIndex 值較低的元素會優先顯示。
  • 可以是正數或負數。
  • 預設值為 0f
  • 只會影響螢幕閱讀器可聚焦的節點,例如螢幕上的元素, 文字或按鈕。舉例來說,如果在資料欄上僅設定 traversalIndex, 除非同時對資料欄設定 isTraversalGroup,否則沒有任何效果。

以下範例說明如何使用 traversalIndex 和 一起isTraversalGroup

範例: Traverse 錶面

錶面是標準週遊順序並未得到的常見情境 這些研究有助於我們找出 能引導後續作業的標準本節中的範例是時間挑選器,可讓使用者周遊 透過錶面上的數字並選擇小時與分鐘的數字 版位。

顯示時間挑選器的錶面。
圖 2. 錶面圖片。

在以下簡化的程式碼片段中,CircularLayout 含有 12 數字將會從 12 開始繪製,並順時針移動圓環:

@Composable
fun ClockFaceDemo() {
    CircularLayout {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier) {
        Text((if (value == 0) 12 else value).toString())
    }
}

因為錶面不會以預設的左至右從邏輯上讀取, TalkBack 會由上至下依序讀出數字。更正 請使用遞增計數器值,如以下程式碼片段所示:

@Composable
fun ClockFaceDemo() {
    CircularLayout(Modifier.semantics { isTraversalGroup = true }) {
        repeat(12) { hour ->
            ClockText(hour)
        }
    }
}

@Composable
private fun ClockText(value: Int) {
    Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) {
        Text((if (value == 0) 12 else value).toString())
    }
}

如要正確設定週遊順序,請先將 CircularLayout 設為 遍歷群組並設定 isTraversalGroup = true然後,每個時鐘文字 繪製在版面配置上,將其對應的 traversalIndex 設為計數器 值。

因為計數器值會持續增加 traversalIndex 會隨著螢幕上加入數字而變大,也就是時鐘值 0 的 traversalIndex 為 0,時鐘值 1 的 traversalIndex 為 1。 這樣一來,TalkBack 的朗讀順序就會設定成順序。現在的數值 系統會按照預期順序讀取 CircularLayout 內的內容。

因為已設定的 traversalIndexes 僅與其他檔案相關 在相同分組內索引時,其餘畫面排序方式 以保留不變換句話說,上述程式碼中的語意變更 程式碼片段只會修改錶面內 已設定 isTraversalGroup = true

請注意,如未將 CircularLayout's 語意設為 isTraversalGroup = truetraversalIndex 變更仍會套用。不過,如果沒有 CircularLayout 可繫結它們,系統會讀取錶面的十二位數 最後,在造訪完畫面上所有其他元素後。發生這種情況 因為所有其他元素的預設 traversalIndex0f,且 時鐘文字元素會在所有其他 0f 元素之後讀取。

範例:自訂懸浮動作按鈕的周遊順序

在此範例中,traversalIndexisTraversalGroup 控制 Material Design 懸浮動作按鈕 (FAB) 的遍歷順序。基礎 以下範例的版面配置如下:

版面配置含有頂端應用程式列、範例文字、懸浮動作按鈕和
  底部應用程式列
圖 3. 採用頂端應用程式列、範例文字、懸浮動作按鈕的版面配置。 以及底部應用程式列。

根據預設,本範例的版面配置的 TalkBack 順序如下:

頂端應用程式列 → 範例文字 0 到 6 → 懸浮動作按鈕 (FAB) → 底部 應用程式列

建議讓螢幕閱讀器先專注於懸浮動作按鈕 (FAB),如要設定 對懸浮動作按鈕 (FAB) 等 Material 元素上的 traversalIndex 執行下列操作:

@Composable
fun FloatingBox() {
    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
        FloatingActionButton(onClick = {}) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
        }
    }
}

在這個程式碼片段中, isTraversalGroup 已設為「true」,並在同一個方塊中設定「traversalIndex」 (-1f 低於預設值 0f) 表示浮動方塊 才出現在畫面上

接著,您可以將浮動方塊和其他元素放入 Scaffold 中 實作 Material Design 版面配置:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColumnWithFABFirstDemo() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Top App Bar") }) },
        floatingActionButtonPosition = FabPosition.End,
        floatingActionButton = { FloatingBox() },
        content = { padding -> ContentColumn(padding = padding) },
        bottomBar = { BottomAppBar { Text("Bottom App Bar") } }
    )
}

TalkBack 會以下列順序與元素互動:

懸浮動作按鈕 (FAB) → 頂端應用程式列 → 範例文字 0 到 6 → 底部應用程式列

其他資源