修改遍歷順序

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

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

在應用程式中使用 isTraversalGroup 和 traversalIndex,控制螢幕閱讀器的遍歷順序。

將元素分組以便周遊

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

「這個句子在」→「這個句子」→「左欄」。→「在右側」

如要正確排序片段,請修改原始程式碼片段,將 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 會依正確順序讀出句子片段:

「這句話在」→「左欄」。→「這個句子」→「在右側」。

自訂遍歷順序

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

traversalIndex 屬性具有下列特性:

  • 系統會優先套用 traversalIndex 值較低的元素。
  • 可以是正數或負數。
  • 預設值為 0f。
  • 如要讓周遊索引影響周遊行為,必須在無障礙服務可選取和聚焦的元件上設定,例如文字或按鈕等螢幕元素。
    • 舉例來說,如果只在 Column 上設定 traversalIndex,則不會有任何效果,除非該資料欄也設定了 isTraversalGroup。

以下範例說明如何搭配使用 traversalIndex 和 isTraversalGroup。

錶面是標準遍歷順序不適用的常見情境。本節範例是時間挑選器,使用者可以瀏覽錶面上的數字,並選取小時和分鐘欄位的數字。

錶面,上方有時間挑選器。
圖 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 繫結,系統會在瀏覽完螢幕上的所有其他元素後,才讀取錶面的十二個數字。這是因為所有其他元素的預設 traversalIndex 為 0f,且時鐘文字元素會在所有其他 0f 元素之後讀取。

API 注意事項

使用遍歷 API 時,請注意下列事項:

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