修改遍歷順序

遍歷順序是指無障礙服務在 UI 元素中瀏覽的順序。在 Compose 應用程式中,元素會按照預期的閱讀順序排列,通常是從左到右,然後從上到下。不過,在某些情況下,Compose 可能需要額外的提示,才能判斷正確的讀取順序。

isTraversalGrouptraversalIndex 是語意屬性,可讓您在 Compose 的預設排序演算法不足的情況下,影響無障礙服務的遍歷順序。isTraversalGroup 會找出需要自訂的語意上重要的群組,而 traversalIndex 會調整這些群組內的個別元素順序。您可以單獨使用 isTraversalGroup 來表示群組中所有元素應一併選取,也可以搭配 traversalIndex 進一步自訂。

在應用程式中使用 isTraversalGrouptraversalIndex 來控制螢幕閱讀器的檢索順序。

用於遍歷的群組元素

isTraversalGroup 是布林值屬性,可定義語意節點是否為遍歷群組。這種節點的功能是做為邊界或邊框,用於整理節點的子項。

在節點上設定 isTraversalGroup = true 表示在移動至其他元素之前,會先造訪該節點的所有子項。您可以在非螢幕閱讀器可聚焦的節點 (例如欄、列或方塊) 上設定 isTraversalGroup

以下範例使用 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 sentence is in the left column」(這句話在左欄),右欄顯示「This sentence is on the right」(這句話在右側)。
圖 1. 包含兩個句子的版面配置 (一個在左欄,一個在右欄)。

由於未設定語意,螢幕閱讀器的預設行為是從左到右、從上到下瀏覽元素。由於這個預設值,TalkBack 會以錯誤的順序朗讀句子片段:

「This sentence is in」→「This sentence is」→「the left column」。→「在右側」。

如要正確排序片段,請修改原始程式碼片段,將 isTraversalGroup 設為 true

@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 會以正確的順序讀出句子片段:

「This sentence is in」→「the left column」。→「This sentence is」→「on the right.」

自訂檢索順序

traversalIndex 是浮點屬性,可讓您自訂 TalkBack 檢視順序。如果將元素分組後,TalkBack 仍無法正常運作,請使用 traversalIndex 搭配 isTraversalGroup,進一步自訂螢幕閱讀器的順序。

traversalIndex 屬性具有下列特性:

  • 系統會優先處理 traversalIndex 值較低的元素。
  • 可以是正面或負面。
  • 預設值為 0f
  • 為了讓遍歷索引影響遍歷行為,您必須在可供無障礙服務選取及聚焦的元件上設定索引,例如文字或按鈕等畫面元素。
    • 舉例來說,如果只開啟 traversalIndexColumn 就不會生效,除非資料欄也設有 isTraversalGroup

以下範例說明如何搭配使用 traversalIndexisTraversalGroup

錶面是標準檢查順序無法運作的常見情境。本節的範例是時間挑選器,使用者可在時鐘面上瀏覽數字,並選取小時和分鐘時段的數字。

錶面,上方有時間挑選器。
圖 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 = true,系統仍會套用 traversalIndex 變更。不過,如果沒有 CircularLayout 來繫結這些元素,系統會在訪問畫面上的所有其他元素後,最後讀取錶面上的十二位數字。這是因為所有其他元素的預設 traversalIndex0f,而時鐘文字元素會在所有其他 0f 元素之後讀取。

API 注意事項

使用 traversal API 時,請考量下列事項:

  • isTraversalGroup = true 應設在包含已分組元素的父項上。
  • traversalIndex 應設在包含語意且會由無障礙服務選取的子元件上。
  • 請確認您要調查的所有元素都位於相同的 zIndex 層級,因為這也會影響語意和檢索順序。
  • 請確認沒有不必要的語意合併,因為這可能會影響要套用哪些元件遍歷索引。