語義

除了可組合函式所載的主要資訊 (例如 Text 可組合函式的文字字串) 之外,提供更多 UI 元素的補充資訊也相當實用。

關於 Compose 中元件意義和角色的資訊稱為語意,這是一種向無障礙、自動填入和測試等服務提供可組合項額外背景資訊的方式。舉例來說,相機圖示在視覺上可能只是一張圖片,但語意意義可能是「拍照」。

將適當的語意與適當的 Compose API 結合,即可向無障礙服務提供盡可能多的元件資訊,讓服務決定如何向使用者呈現。

Material、Compose UI 和 Foundation API 都內建語意,可根據其特定角色和功能進行調整,但您也可以根據特定需求,修改現有 API 的語意,或為自訂元件設定新的語意。

語意屬性

語意屬性可傳達對應可組合元件的意義。舉例來說,Text 可組合項包含語意屬性 text,因為這是可組合項的含義Icon 包含 contentDescription 屬性 (如果由開發人員設定),該屬性透過文字形式提供圖示的含義。

請考量語意屬性如何傳達可組合項的含義。請考慮使用 Switch。使用者所看到的畫面如下:

圖 1. 表示 Switch 處於「開啟」和「關閉」狀態。

如要說明這個元素的含義,您可以說:「這是切換按鈕,它是一個可切換元素,目前處於『開啟』狀態。只要按一下即可與其互動。」

這就是語義屬性的用途。這個切換開關元素的語義節點包含下列屬性,用版面配置檢查器可以看到:

版面配置檢查器顯示切換按鈕可組合項的語意屬性
圖 2. 版面配置檢查器顯示 Switch 可組合項的語意屬性。

Role 代表元素類型。StateDescription 說明瞭應該如何參考「開啟」狀態。根據預設,這是「開啟」一詞的本地化版本,但您可以根據背景資訊提供更明確的字詞 (例如「已啟用」)。ToggleableState 是切換按鈕的目前狀態。OnClick 屬性會參考與這個元素互動的方法。

追蹤應用程式中不同元件的語義屬性,藉此發掘許多強大的功能:

  • 無障礙服務會使用這些屬性,代表畫面上顯示的 UI,並讓使用者與其互動。對於 Switch 可組合項,Talkback 可能會宣告:「開啟;切換;輕觸兩下即可切換」。使用者只要輕觸兩下螢幕即可關閉切換按鈕。
  • 測試架構會使用屬性尋找節點、與節點互動以及進行宣告:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

建立在 Compose 基礎程式庫之上的可組合元件和修飾詞,預設會為您設定相關屬性。您可以視需要手動變更這些屬性,以改善特定用途的無障礙支援功能,或變更可組合項的合併或清除策略

如要向無障礙服務傳達元件特定內容類型,您可以套用各種不同的語意。這些新增項目可支援主要語意資訊,並協助無障礙服務微調元件顯示、朗讀或互動的方式。

如需完整的語義屬性清單,請參閱 SemanticsProperties 物件。如需完整的無障礙操作清單,請參閱 SemanticsActions 物件。

標題

應用程式經常包含含有大量文字的畫面,例如長篇文章或新聞頁面,這些畫面通常會以標題劃分為不同的子區段:

網誌文章,文章文字位於可捲動的容器中。
圖 3. 網誌文章,文章文字位於可捲動的容器中。

無障礙功能的使用者無法順利瀏覽這類畫面。為改善瀏覽體驗,部分無障礙服務可讓使用者直接在各個部分或標題之間瀏覽。如要啟用這項功能,請定義元件的語意屬性,指出元件為 heading

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

快訊和彈出式視窗

如果元件是警示或彈出式視窗 (例如 Snackbar),您可能需要向無障礙服務發出信號,表示可以向使用者傳達新結構或內容更新。

您可以使用 liveRegion 語意屬性標示類似警示的元件。這可讓無障礙服務自動通知使用者此元件或其子項的變更:

PopupAlert(
    message = "You have a new message",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite
    }
)

在大多數情況下,您應使用 liveRegionMode.Polite,因為在這種情況下,使用者只需將注意力短暫轉移至畫面上的警示或重要變動內容。

請盡量少用 liveRegion.Assertive,以免造成干擾。這項功能應用於必須讓使用者瞭解有時效性內容的情況:

PopupAlert(
    message = "Emergency alert incoming",
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Assertive
    }
)

請勿將即時區塊用於經常更新的內容,例如倒數計時器,以免不斷提供意見回饋而造成使用者負擔。

類似視窗的元件

類似 ModalBottomSheet 的窗格式自訂元件需要額外信號,才能與周圍內容區分開。為此,您可以使用 paneTitle 語意,讓無障礙服務能適當地呈現任何相關視窗或窗格變更,以及主要語意資訊:

ShareSheet(
    message = "Choose how to share this photo",
    modifier = Modifier
        .fillMaxWidth()
        .align(Alignment.TopCenter)
        .semantics { paneTitle = "New bottom sheet" }
)

如需參考,請參閱 Material 3 如何使用 paneTitle 元件。

錯誤元件

針對其他類型的內容 (例如類似錯誤的元件),您可能需要為有無障礙需求的使用者擴充主要語意資訊。定義錯誤狀態時,您可以將 error 語意通知給無障礙服務,並提供擴充的錯誤訊息。

在這個範例中,TalkBack 會朗讀主要錯誤文字資訊,接著朗讀其他詳細訊息:

Error(
    errorText = "Fields cannot be empty",
    modifier = Modifier
        .semantics {
            error("Please add both email and password")
        }
)

進度追蹤元件

針對追蹤進度的自訂元件,您可能會想通知使用者進度變更,包括目前進度值、範圍和步長。您可以使用 progressBarRangeInfo 語意來執行這項操作,確保無障礙服務能夠瞭解進度變更,並據此更新使用者。不同的輔助技術也可能會以獨特的方式提示增加和減少進度。

ProgressInfoBar(
    modifier = Modifier
        .semantics {
            progressBarRangeInfo =
                ProgressBarRangeInfo(
                    current = progress,
                    range = 0F..1F
                )
        }
)

清單和項目資訊

在包含許多項目的自訂清單和格線中,如果輔助服務也能收到更詳細的資訊,例如項目和索引的總數,可能會很有幫助。

在清單和項目上分別使用 collectionInfocollectionItemInfo 語意,無障礙服務就能在這個長清單中,除了文字語意資訊外,也向使用者告知他們在整個系列中的項目索引:

MilkyWayList(
    modifier = Modifier
        .semantics {
            collectionInfo = CollectionInfo(
                rowCount = milkyWay.count(),
                columnCount = 1
            )
        }
) {
    milkyWay.forEachIndexed { index, text ->
        Text(
            text = text,
            modifier = Modifier.semantics {
                collectionItemInfo =
                    CollectionItemInfo(index, 0, 0, 0)
            }
        )
    }
}

狀態說明

可組合項可定義語意的 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() }
            )
    ) {
        /* ... */
    }
}

自訂操作

自訂動作可用於更複雜的觸控螢幕手勢,例如滑動關閉或拖曳放置,因為這些動作對運動障礙或其他障礙的使用者來說,可能難以互動。

如要讓滑動關閉手勢更容易存取,您可以將其連結至自訂動作,並在該動作中傳遞關閉動作和標籤:

SwipeToDismissBox(
    modifier = Modifier.semantics {
        // Represents the swipe to dismiss for accessibility
        customActions = listOf(
            CustomAccessibilityAction(
                label = "Remove article from list",
                action = {
                    removeArticle()
                    true
                }
            )
        )
    },
    state = rememberSwipeToDismissBoxState(),
    backgroundContent = {}
) {
    ArticleListItem()
}

接著,TalkBack 等無障礙服務會醒目顯示元件,並提示選單中還有更多動作,代表滑動以關閉選單中的動作:

TalkBack 動作選單的視覺化呈現
圖 4. TalkBack 動作選單的視覺化呈現。

自訂動作的另一個用途是針對長清單中的項目,提供更多可用的動作,因為使用者可能會覺得逐一重複執行每個項目的動作很麻煩:

=畫面上切換控制導覽功能的視覺化呈現
圖 5. 開關功能導覽畫面上的視覺化效果。

為了改善導覽體驗 (這對以互動為基礎的輔助技術特別有幫助,例如 Switch Access 或 Voice Access),您可以在容器上使用自訂動作,將動作從個別的遍歷移出,並放入單獨的動作選單:

ArticleListItemRow(
    modifier = Modifier
        .semantics {
            customActions = listOf(
                CustomAccessibilityAction(
                    label = "Open article",
                    action = {
                        openArticle()
                        true
                    }
                ),
                CustomAccessibilityAction(
                    label = "Add to bookmarks",
                    action = {
                        addToBookmarks()
                        true
                    }
                ),
            )
        }
) {
    Article(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = openArticle,
    )
    BookmarkButton(
        modifier = Modifier.clearAndSetSemantics { },
        onClick = addToBookmarks,
    )
}

在這種情況下,請務必使用 clearAndSetSemantics 修飾符手動清除原始子項的語意,因為您會將這些子項移至自訂動作。

以切換控制功能為例,選取容器後會開啟選單,並列出可用的巢狀動作:

切換控制功能醒目顯示的文章清單項目
圖 6. 切換控制功能醒目顯示文章清單項目。
切換控制功能動作選單的視覺化呈現。
圖 7. 切換控制功能動作選單的視覺化呈現。

語意樹

Composition 描述了應用程式的 UI,可透過執行各個可組合元件產生。組合是一種樹狀結構,內含可描述 UI 的可組合元件。

Composition 旁邊有一個平行樹狀結構,也稱為「語義樹狀圖」。這個樹狀圖以 無障礙服務和測試架構能夠理解的替代方式描述 UI。無障礙服務會使用樹狀圖向有特定需求的使用者描述該應用程式。測試架構會使用樹狀圖與您的應用程式互動,並對其做出判斷提示。語意樹狀圖未包含如何繪製可組合元件的資訊,但包含可組合元件的語意含義資訊。

一般 UI 階層及其語意樹狀結構
圖 8.一般 UI 階層及其語意樹狀結構。

如果您的應用程式是由 Compose 基礎和材質庫的可組合元件和修飾元組成,系統會自動為您產生並填入語義樹狀圖。不過,當您新增自訂低層級可組合元件時,就必須手動提供其語意。在某些情況下,您的樹狀結構可能無法正確或完整反映螢幕上的元素含義,這時您只要調整樹狀結構即可。

以這個自訂日曆可組合元件為例:

包含可選取日可組合元件的自訂日曆
圖 9.包含可選取日可組合元件的自訂日曆。

在這個例子中,系統會使用Layout可組合元件且直接繪製入Canvas,從而將整個日曆作為單一低層級元件納入其中。如果您不採取任何其他行動,無障礙服務將無法收到有關可組合元件內容的資訊,也無法收到使用者在日曆中的選取資訊。舉例來說,如果使用者點選包含 17 的日期,無障礙架構只會接收整個日曆控制項的說明資訊。在這種情況下,TalkBack 無障礙功能服務只會說出「日曆」音訊,較好的情況下會說出「四月份日曆」,而使用者無法知道到底選取了哪一天。如要讓這元件更容易存取,您必須手動新增語義資訊。

合併及未合併的樹狀結構

如前文所述,UI 樹狀圖中每個可組合元件可能沒有或是有多個語義屬性設定。如果可組合元件沒有語意屬性設定,系統無法將其列入語意樹狀結構。那樣的話,語義樹狀圖僅包含真正含有語義含義的節點。但是,有時為了正確傳遞熒幕上呈現的含義,合併某些節點的子樹系並將其視為一體還是很有幫助的。那樣的話,您就可以將一組節點當做一個整體進行推理,而非單獨處理每個子節點。根據經驗,在使用無障礙服務時,這個樹狀圖中的每個節點都代表了一個可聚焦元素。

Button 就是這種可組合函式的範例。您可以將按鈕視為單一元素,即使它可能包含多個子節點:

Button(onClick = { /*TODO*/ }) {
    Icon(
        imageVector = Icons.Filled.Favorite,
        contentDescription = null
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

在語意樹狀圖中,按鈕的子系屬性會被合併,且按鈕在樹狀圖中會顯示為單一的分葉節點:

合併的單一葉語意表示法
圖 10. 合併的單葉語意表示法。

可組合元件和修飾元可透過呼叫 Modifier.semantics (mergeDescendants = true) {} 來合併其子系的語義屬性。如果將這個屬性設定為 true,則表示應該合併語義屬性。在 Button 範例中,Button 可組合項在內部使用了含有 semantics 修飾符的 clickable 修飾符。因此,系統會合併按鈕的子系節點。請參閱無障礙說明文件,進一步瞭解何時應在可組合項中變更合併行為

基礎與資料 Compose 程式庫中的數個修飾元和可組合元件具有該屬性設定。例如,clickabletoggleable 修飾元會自動合併它們的子系。此外,ListItem 可組合元件也會合併其子系。

檢查樹狀圖

語義樹狀圖其實是兩個不同的樹狀圖。其中一個是 已合併的語義樹狀圖,該樹狀圖在 mergeDescendants 設定為 true 時合併子系節點。另外還有一個未合併的語義樹狀圖,該樹狀圖不會套用合併規則,但會保留所有節點。無障礙服務會使用未合併的樹狀圖並套用自己的合併演算法,並將 mergeDescendants 屬性納入考量。根據預設,測試架構會使用合併的樹狀圖。

您可以使用 printToLog() 方法檢查這兩種樹狀圖。根據預設,和先前的範例一樣,系統會記錄已合併的樹狀圖。如要改為列印未合併的樹狀圖,請將 onRoot() 比對器的 useUnmergedTree 參數設為 true

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

版面配置檢查器可讓您在檢視模式篩選器中選取偏好的樹狀圖,以顯示合併和未合併的語義樹狀圖:

版面配置檢查器檢視選項,允許顯示已合併和未合併的語義樹狀圖
圖 11. 版面配置檢查器檢視選項,允許顯示已合併和未合併的語義樹狀圖。

針對樹狀圖中的每個節點,版面配置檢查器會顯示其合併語義和屬性面板中該節點的語義設定:

合併及設定語意屬性
圖 12. 已合併及設定語意屬性。

根據預設,測試架構中的比對器會使用已合併的語義樹狀圖。因此,您可以透過比對 Button 內顯示的文字來與之互動:

composeTestRule.onNodeWithText("Like").performClick()

如要覆寫這項行為,請將比對器的 useUnmergedTree 參數設為 true,做法與 onRoot 比對器相同。

調整樹狀圖

如上所述,您可以覆寫或清除特定語義屬性,或變更樹狀圖的合併行為。當您建立自訂可組合元件時,這一點尤其重要。如果沒有設定正確的屬性和合併行為,您的應用程式可能會無法存取,而測試行為可能與您預期的不同。如要進一步瞭解測試,請參閱測試指南