Composition 描述了應用程式的 UI,可透過執行各個可組合元件產生。Composition 是一種樹狀結構,內含可描述 UI 的可組合元件。
Composition 旁邊有一個平行樹狀結構,也稱為 語義樹狀圖。這個樹狀圖以 無障礙 服務和 測試 架構能夠理解的替代方式描述 UI。無障礙服務會使用樹狀圖向有特定需求的使用者描述該應用程式。測試架構會使用樹狀圖與您的應用程式互動,並對其做出判斷提示。語義樹狀圖未包含如何 繪製 可組合元件的資訊,但包含了可組合元件的 語義含義 的資訊。
圖 1. 一般 UI 階層及其語意樹狀結構。
如果您的應用程式是由 Compose 基礎和材質庫的可組合元件和修飾元組成,系統會自動為您產生並填入語義樹狀圖。不過,當您新增自訂低層級可組合元件時,就必須手動提供其語意。在某些情況下,您的樹狀結構可能無法正確或完整反映螢幕上的元素含義,這時您只要調整樹狀結構即可。
以這個自訂日曆可組合元件為例:
圖 2. 包含可選取日可組合元件的自訂日曆。
在這個例子中,系統會使用Layout
可組合元件且直接繪製入Canvas
,從而將整個日曆作為單一低層級元件納入其中。如果您不採取任何其他行動,無障礙服務將無法收到有關可組合元件內容的資訊,也無法收到使用者在日曆中的選取資訊。舉例來說,如果使用者點擊包含 17 的日期,無障礙架構只會接收整個日曆控制項的說明資訊。在這種情況下,TalkBack 無障礙功能服務只會說出「日曆」音訊,較好的情況下會說出「四月份日曆」,而使用者無法知道到底選取了哪一天。如要讓這元件更容易存取,您必須手動新增語義資訊。
語義屬性
UI 樹狀圖中的所有節點都具有語義含義,這些節點在語義樹狀圖中具有平行節點。語義樹狀圖中的節點所包含的屬性能夠傳達對應可組合元件的意義。舉例來說,Text
可組合元件包含語義屬性 text
,因為這是可組合元件的 含義。Icon
包含 contentDescription
屬性 (若由開發人員設定),該屬性透過文字形式提供 Icon
的含義。建立在 Compose 基礎程式庫之上的可組合元件和修飾詞已經為您設定了相關屬性。或者,您也可以使用 semantics
和 clearAndSetSemantics
修飾詞自行設定或覆寫屬性。舉例來說,您可以在節點中新增 自訂無障礙動作、為可切換元素提供替代 狀態說明,或是指出某個特定文字可組合元件應被視為 標題。
如要呈現語義樹狀圖,請使用 版面配置檢查器工具,或使用測試中的 printToLog()
方法。這會列印出 Logcat 中的目前語義樹狀圖。
class MyComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun MyTest() {
// Start the app
composeTestRule.setContent {
MyTheme {
Text("Hello world!")
}
}
// Log the full semantics tree
composeTestRule.onRoot().printToLog("MY TAG")
}
}
這項測試的輸出內容如下:
Printing with useUnmergedTree = 'false'
Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
|-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
Text = '[Hello world!]'
Actions = [GetTextLayoutResult]
讓我們來看看一個範例,瞭解語意屬性如何用於傳達可組合元件的含義。假設有 Switch
。使用者所看到的畫面如下:
圖 3. 表示切換按鈕處於「開啟」和「關閉」狀態。
如要說明這個元素的含義,您可以說:「這是切換按鈕,它是一個可切換元素,目前處於『開啟』狀態。只要按一下即可與其互動。」
這就是語義屬性的用途。這個切換開關元素的語義節點包含下列屬性,用版面配置檢查器可以看到:
圖 4. 版面配置檢查器顯示了切換按鈕可組合元件的語義屬性。
Role
代表我們正在尋找的元素類型。StateDescription
說明了應該如何參考「開啟」狀態。根據預設,這只是「開啟」一詞的本地化版本,但您可以根據背景資訊提供更明確的字詞(例如「已啟用」)。ToggleableState
是切換按鈕的目前狀態。OnClick
屬性會參考與這個元素互動的方法。如需完整的語義屬性清單,請參閱 SemanticsProperties
物件。
如需完整的無障礙操作清單,請參閱 SemanticsActions
物件。
追蹤應用程式中不同元件的語義屬性,藉此發掘許多強大的功能。以下列舉部分範例:
- Talkback 使用屬性來朗讀螢幕上顯示的內容,讓使用者順利與其互動。我們的切換按鈕可能會顯示為「開啟;切換;輕觸兩下即可切換」。使用者只要輕觸兩下螢幕即可關閉切換按鈕。
-
測試架構會使用屬性尋找節點、與節點互動以及進行宣告。以下是切換開關的測試範例:
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
合併及未合併的語義樹狀圖
如前文所述,UI 樹狀圖中每個可組合元件可能沒有或是有多個語義屬性設定。如果可組合元件沒有語意屬性設定,系統無法將其列入語意樹狀結構。那樣的話,語義樹狀圖僅包含真正含有語義含義的節點。但是,有時為了正確傳遞熒幕上呈現的含義,合併某些節點的子樹系並將其視為一體還是很有幫助的。那樣的話,我們就可以將一組節點當做一個整體進行推理,而非單獨處理每個子節點。根據經驗,在使用無障礙服務時,這個樹狀圖中的每個節點都代表了一個可聚焦元素。
按鈕便是這種可組合元件的一個範例。我們希望將按鈕視為單一元素,即使它可能包含多個子節點:
Button(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = null
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
在我們的語義樹狀圖中,按鈕的子系屬性會被合併,且按鈕在樹狀圖中會顯示為單一的分葉節點:
可組合元件和修飾元可透過呼叫 Modifier.semantics
(mergeDescendants = true) {}
來合併其子系的語義屬性。如果將這個屬性設定為 true
,則表示應該合併語義屬性。在我們的 Button
範例中,Button
可組合元件在內部使用了含有 semantics
修飾元的 clickable
修飾元。因此,系統會合併該按鈕的子系節點。請參閱無障礙說明文件,進一步瞭解何時應在可組合元件中 變更合併行為。
基礎與資料 Compose 程式庫中的數個修飾元和可組合元件具有該屬性設定。例如,clickable
和 toggleable
修飾元會自動合併它們的子系。此外,ListItem
可組合元件也會合併其子系。
檢查樹狀圖
談論語義樹狀圖時,我們實際上是在探討兩種不同的樹狀圖。其中一個是 已合併 的語義樹狀圖,該樹狀圖在 mergeDescendants
設定位 true
時合併子系節點。另外還有一個 未合併 的語義樹狀圖,該樹狀圖不會套用合併規則,但會保留所有節點。無障礙服務會使用未合併的樹狀圖並套用自己的合併演算法,並將 mergeDescendants
屬性納入考量。根據預設,測試架構會使用合併的樹狀圖。
您可以使用 printToLog()
方法檢查這兩種樹狀圖。根據預設,和先前的範例一樣,系統會記錄已合併的樹狀圖。如要改為列印未合併的樹狀圖,請將 onRoot()
比對器的 useUnmergedTree
參數設為 true
:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
版面配置檢查器可讓您在檢視模式篩選器中選取偏好的樹狀圖,以顯示合併和未合併的語義樹狀圖:
圖 5. 版面配置檢查器檢視選項,允許顯示已合併和未合併的語義樹狀圖。
針對樹狀圖中的每個節點,版面配置檢查器會顯示其合併語義和屬性面板中該節點的語義設定:
根據預設,測試架構中的比對器會使用已合併的語義樹狀圖。因此,您可以透過比對按鈕內顯示的文字來與之互動:
composeTestRule.onNodeWithText("Like").performClick()
如要覆寫此行為,請將比對器的 useUnmergedTree
參數設為 true
,做法與之前使用 onRoot
比對器相同。
合併行為
當一個可組合元件表示應該合併其子系,那麼這種合併到底是如何進行的?
每個語義屬性都有已定義的合併策略。舉例來說,ContentDescription
屬性會將所有子系 ContentDescription 值新增至清單。如要檢查語義屬性的合併策略,請前往 SemanticsProperties.kt
查看其 mergePolicy
實作項目。屬性選取有以下幾種:總是選取父值或子值、將值合併至清單或字串、完全不允許合併且擲回例外狀況,或任何其他自訂合併策略。
重要的是,本身設定了 mergeDescendants = true
的子系未包含在合併作業中。一起來看看以下範例:
圖 6. 含有圖片、部分文字和書籤圖示的清單項目。
另外還有一個可點擊的清單項目。使用者按一下該列時,應用程式會前往文章詳細資料頁面,使用者便可閱讀文章。清單項目中有一個可用來將文章加入書籤的按鈕。在這個範例中,我們有一個巢狀可點擊元素,因此按鈕會分別顯示在合併的樹狀結構中。該列中其餘內容已被合併:
圖 7. 合併的樹狀圖在列節點內的清單中包含多個文字。未合併的樹狀圖包含每個文字可組合元件的獨立節點。
調整語義樹狀圖
如上所述,您可以覆寫或清除特定語義屬性,或變更樹狀圖的合併行為。當您建立自訂可組合元件時,這一點尤其重要。如果沒有設定正確的屬性和合併行為,您的應用程式可能會無法存取,而測試行為可能與您預期的不同。要閱讀關於在何處調整語義樹狀圖的常見使用案例,請參閱 無障礙說明文件。如要進一步瞭解測試,請查看 測試指南。