Compose 中的鍵盤焦點管理

1. 簡介

使用者可以使用硬體鍵盤與應用程式互動,通常是在平板電腦和 ChromeOS 裝置等大螢幕裝置上,但也可以在 XR 裝置上。重點是讓使用者可以透過硬體鍵盤瀏覽應用程式,並且能像使用觸控螢幕一樣順利。此外,如果您要為電視和車用螢幕設計應用程式,而這些裝置可能沒有觸控輸入功能,而是依賴 D-Pad 或旋轉編碼器,則需要套用類似的鍵盤瀏覽原則。

Compose 可讓您以統一方式處理硬體鍵盤、D-Pad 和旋轉編碼器的輸入內容。若要讓使用者在使用這些輸入方式時獲得良好體驗,關鍵原則是讓使用者能直覺且一致地將鍵盤焦點移至要互動的互動式元件。

在本程式碼實驗室中,您將瞭解以下內容:

  • 如何實作常見的鍵盤焦點管理模式,以便提供直覺且一致的瀏覽體驗
  • 如何測試鍵盤焦點移動行為是否如預期

必要條件

  • 具備使用 Compose 建構應用程式的經驗
  • 具備 Kotlin 的基本知識,包括 lambda 和協同程式

建構內容

您可以實作下列常見的鍵盤焦點管理模式:

  • 鍵盤焦點移動:以 Z 字型模式從開頭到結尾、從上到下移動
  • 邏輯初期焦點:將焦點設為使用者可能會互動的 UI 元素
  • 焦點還原:將焦點移至使用者先前互動的 UI 元素

課程內容

  • Compose 中的焦點管理基本概念
  • 如何將 UI 元素設為焦點目標
  • 如何要求焦點移動 UI 元素
  • 如何在一組 UI 元素中將鍵盤焦點移至特定 UI 元素

軟硬體需求

  • Android Studio Ladybug 以上版本
  • 使用下列任一裝置執行範例應用程式:
  • 配備硬體鍵盤的大螢幕裝置
  • 適用於大螢幕裝置的 Android 虛擬裝置,例如可調整大小的模擬器

2. 設定

  1. 複製 large-screen-codelabs GitHub 存放區:
git clone https://github.com/android/large-screen-codelabs

您也可以下載並取消封存 large-screen-codelabs ZIP 檔案:

  1. 前往 focus-management-in-compose 資料夾:
  2. 在 Android Studio 中開啟專案。focus-management-in-compose 資料夾包含一個專案。
  3. 如果沒有 Android 平板電腦、摺疊式裝置或配備硬體鍵盤的 ChromeOS 裝置,請在 Android Studio 中開啟「裝置管理工具」,然後在「手機」類別中建立「可調整大小」裝置。

Android Studio 的裝置管理工具會顯示手機類別中可用的虛擬裝置清單。可調整大小的模擬器就屬於這個類別。圖 1. 在 Android Studio 中設定可調整大小的模擬器。

3. 探索範例程式碼

這個專案有兩個模組:

  • start:包含專案的範例程式碼。您需要修改此程式碼以完成程式碼實驗室。
  • solution:包含本程式碼實驗室完成後的程式碼。

這個範例應用程式包含三個分頁:

  • 焦點目標
  • 焦點遍歷順序
  • 焦點群組

應用程式啟動時,系統會顯示焦點目標分頁。

範例應用程式的第一個檢視畫面。這個畫面有三個分頁,並選取了第一個分頁 (焦點目標分頁)。分頁會顯示三個置於一欄中的資訊卡。

圖 2. 應用程式啟動時,系統會顯示「焦點目標」分頁。

ui 套件包含您要互動的下列 UI 程式碼:

4. 焦點目標

焦點目標是鍵盤焦點可移動前往的目標 UI 元素。使用者可以利用 Tab 鍵或方向 (箭頭) 鍵移動鍵盤焦點:

  • Tab 鍵:將焦點移至下一個焦點目標或上一個焦點目標,且「僅限單一維度」
  • 方向鍵:焦點可在「二維空間」中移動:上、下、左和右。

分頁是焦點目標。在範例應用程式中,當分頁獲得焦點時,分頁的背景會在視覺上更新。

GIF 動畫檔案,顯示鍵盤焦點如何在 UI 元素之間移動。焦點在三個分頁之間移動,然後將焦點放在第 1 張資訊卡上。

圖 3. 當焦點移至焦點目標時,元件背景會變更。

根據預設,互動式 UI 元素是焦點目標

互動式元件預設為焦點目標。換句話說,如果使用者可以輕觸 UI 元素,該元素就是焦點目標。

範例應用程式在「焦點目標」分頁中顯示三張資訊卡。「第 1 張卡」和「第 3 張卡」是焦點目標,「第 2 張卡」則不是。當使用者使用 Tab 鍵將焦點從「第 1 張卡」移至「第 3 張卡」時,第 3 張卡的背景會更新。

GIF 動畫,顯示焦點目標分頁中的初始鍵盤焦點移動情形。當使用者在第 1 張卡上按下 Tab 鍵時,系統會略過第 2 張卡,並從第 1 張卡移動到第 3 張卡。

圖 4. 應用程式焦點目標排除「第 2 張資訊卡」

將第 2 張資訊卡修改為焦點目標

您可以將「第 2 張資訊卡」變更為互動式 UI 元素,以便將其設為焦點目標。最簡單的方法是使用 clickable 修飾符,如下所示:

  1. 開啟 tabs 套件中的 FocusTargetTab.kt
  2. 使用 clickable 修飾符修改 SecondCard 可組合函式,如下所示:
@Composable
fun FocusTargetTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
        SecondCard(
            modifier = Modifier
                .width(240.dp)
                .clickable(onClick = onClick)
        )
        ThirdCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
    }
}

開始執行

除了第 1 張卡第 3 張卡,使用者現在也可以將焦點移至第 2 張卡。您可以在「焦點目標」分頁中試試這項功能,確認您可以使用 Tab 鍵,將焦點從「第 1 張卡」移至「第 2 張卡」

GIF 動畫,顯示修改後的鍵盤焦點移動情形。當使用者在第 1 張資訊卡上按下 Tab 鍵時,焦點就會從第 1 張資訊卡移開。

圖 5. 使用 Tab 鍵,將焦點從「第 1 張卡」移至「第 2 張卡」

5. Z 型焦點遍歷

在從左至右的語言設定中,使用者預期鍵盤焦點會從左至右和由上至下移動。這種焦點遍歷順序稱為 z 型模式

不過,Compose 在判斷 Tab 鍵的下一個焦點目標時,會忽略版面配置,並根據可組合函式呼叫的順序,採用單一維度焦點遍歷。

單一維度焦點遍歷

單一維度焦點遍歷順序取決於可組合函式呼叫的順序,而非應用程式版面配置。

在範例應用程式中,焦點會按照「焦點遍歷順序」分頁中的順序移動:

  1. 第 1 張資訊卡
  2. 第 4 張資訊卡
  3. 第 3 張資訊卡
  4. 第 2 張資訊卡

GIF 動畫,顯示鍵盤焦點的移動方式與使用者預期不同。從第 1 張卡移動到第 3 張卡,然後是第 4 張卡和第 2 張卡。這可能會違背使用者的預期。

圖 6. 焦點遍歷會依照可組合函式的順序進行。

FocusTraversalOrderTab 函式會實作範例應用程式的「焦點遍歷分頁」。函式會依下列順序呼叫資訊卡的可組合函式:FirstCardFourthCardThirdCardSecondCard

@Composable
fun FocusTraversalOrderTab(
    modifier: Modifier = Modifier
) {
    Row(
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        Column(
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            FirstCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier
                    .width(240.dp)
                    .offset(x = 256.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier
                    .width(240.dp)
                    .offset(y = (-151).dp)
            )
        }
        SecondCard(
            modifier = Modifier.width(240.dp)
        )
    }
}

以 Z 字型模式移動焦點

您可以按照下列步驟,在範例應用程式的「焦點遍歷順序」分頁中整合 Z 型焦點移動方式:

  1. 開啟「tabs.FocusTraversalOrderTab.kt
  2. ThirdCardFourthCard 可組合函式中移除偏移修飾符。
  3. 將分頁的版面配置從目前的兩欄一列變更為一欄兩列。
  4. FirstCardSecondCard 可組合函式移至第一列。
  5. ThirdCardFourthCard 可組合函式移至第二列。

修改後的程式碼如下:

@Composable
fun FocusTraversalOrderTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            FirstCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp),
            )
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
        }
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
        }
    }
}

開始執行

使用者現在可以以 Z 字型模式,從右到左、從上到下移動焦點。您可以在「焦點遍歷順序」分頁中按 Tab 鍵試試這項功能,確認焦點是否會以下列順序移動:

  1. 第 1 張資訊卡
  2. 第 2 張資訊卡
  3. 第 3 張資訊卡
  4. 第 4 張資訊卡

GIF 動畫:顯示鍵盤焦點在修改後的移動方式。焦點會以 z 字型順序從左到右、從上到下移動。

圖 7. 以 Z 字型模式進行焦點遍歷。

6. focusGroup

在「焦點群組」分頁上使用 right 方向鍵,將焦點從「第 1 張卡」移至「第 3 張卡」。由於兩張資訊卡並未並排顯示,因此這項動作可能會讓使用者感到困惑。

GIF 動畫,使用向右方向鍵,將鍵盤焦點從第 1 張卡移至第 3 張卡。這兩張資訊卡位在不同的列。

圖 8. 焦點從「第 1 張卡」意外移至「第 3 張卡」

二維焦點遍歷是指版面配置資訊

按下方向鍵會觸發二維焦點遍歷。這是電視上常用的焦點遍歷方式,因為使用者會透過 D-Pad 與應用程式互動。按下鍵盤方向鍵也會觸發二維焦點遍歷,因為這類按鍵會模擬 D-Pad 的瀏覽功能。

在二維焦點遍歷作業中,系統會參照 UI 元素的幾何資訊,並判斷焦點目標以移動焦點。舉例來說,您可以使用 down 方向鍵,將焦點從焦點目標分頁移至「第 1 張資訊卡」,而按向上方向鍵則可將焦點移至焦點目標分頁。

這張 GIF 顯示,透過向下方向鍵,焦點從焦點目標分頁移至第 1 張資訊卡,然後再透過向上方向鍵返回分頁。這兩個焦點目標是垂直方向上最接近的目標。

圖 9.使用向下和向上方向鍵進行焦點遍歷。

與使用 Tab 鍵的單向焦點遍歷功能不同,二維焦點遍歷功能不會循環。舉例來說,當「第 2 張資訊卡」獲得焦點時,使用者無法透過向下鍵移動焦點。

這段 GIF 顯示,即使使用者按下向下方向鍵,焦點仍會停留在第 2 張資訊卡,因為這張資訊卡下方已沒有焦點目標。

圖 10. 焦點位於「第 2 張資訊卡」時,向下方向鍵無法移動焦點。

焦點目標位於相同層級

以下程式碼會實作上述畫面。有四個焦點目標:FirstCardSecondCardThirdCardFourthCard。這四個焦點目標位於同一層級,ThirdCard 是版面配置中 FirstCard 右側的第一個項目。因此,使用 right 方向鍵時,焦點會從「第 1 張卡」移至「第 3 張卡」

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

使用 focusGroup 修飾符將焦點目標分組

您可以按照下列步驟,改掉令人困惑的焦點移動方式:

  1. 開啟「tabs.FocusGroup.kt
  2. 使用 focusGroup 修飾符修改 FocusGroupTab 可組合函式中的 Column 可組合函式。

更新後的程式碼如下:

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            modifier = Modifier.focusGroup(),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

focusGroup 修飾符會建立焦點群組,由已修改元件內部的焦點目標組成。焦點群組中的焦點目標和焦點群組外的焦點目標位於不同層級,且 FirstCard 可組合函式右側沒有放置焦點目標。因此,焦點不會透過 right 方向鍵從「第 1 張資訊卡」移至任何資訊卡。

開始執行

在範例應用程式的「焦點群組分頁」中,使用 right 方向鍵時,焦點不會從「第 1 張卡」移至「第 3 張卡」

7. 要求焦點

使用者無法使用鍵盤或 D-Pad 選取要互動的任意 UI 元素。使用者必須先將鍵盤焦點移至互動式元件,才能與元素互動。

舉例來說,使用者必須先將焦點從「焦點目標分頁」移至「第 1 張資訊卡」,才能與資訊卡互動。您可以透過邏輯設定初期焦點,減少啟動使用者主要工作的動作數量。

GIF 動畫,顯示使用者選取分頁後,應按下 Tab 鍵三次,將鍵盤焦點移至分頁中的第 1 張資訊卡。

圖 11. 按下 Tab 鍵三次,焦點會移至「第 1 張資訊卡」

使用 FocusRequester 要求焦點

您可以使用 FocusRequester 要求焦點移動至某 UI 元素。請先將 FocusRequester 物件與 UI 元素建立關聯,再呼叫 requestFocus() 方法。

將初期焦點設為第 1 張資訊卡

您可以按照下列步驟,將初期焦點設為「第 1 張資訊卡」

  1. 開啟「tabs.FocusTarget.kt
  2. FocusTargetTab 可組合函式中宣告 firstCard 值,並使用 remember 函式傳回的 FocusRequester 物件初始化該值。
  3. 使用 focusRequester 修飾符修飾 FirstCard 可組合函式。
  4. firstCard 值指定為 focusRequester 修飾符的引數。
  5. 使用 Unit 值呼叫 LaunchedEffect 可組合函式,並在傳遞至 LaunchedEffect 可組合函式的 lambda 中,針對 firstCard 值呼叫 requestFocus() 方法。

在第二步和第三步驟中,系統會建立 FocusRequester 物件,並將其與 UI 元素建立關聯。在第五個步驟中,當 FocusdTargetTab 可組合函式首次組合時,系統會要求將焦點移至相關聯的 UI 元素。

更新後的程式碼如下所示:

@Composable
fun FocusTargetTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    val firstCard = remember { FocusRequester() }

    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier
                .width(240.dp)
                .focusRequester(focusRequester = firstCard)
        )
        SecondCard(
            modifier = Modifier
                .width(240.dp)
                .clickable(onClick = onClick)
        )
        ThirdCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
    }

    LaunchedEffect(Unit) {
        firstCard.requestFocus()
    }
}

開始執行

現在,選取分頁時,鍵盤焦點會移至「焦點目標」分頁標籤中的「第 1 張資訊卡」您可以試著切換分頁。此外,應用程式啟動時會選取「第 1 張卡」

GIF 動畫,顯示使用者選取焦點目標分頁時,鍵盤焦點會自動移至第 1 張資訊卡。

圖 12. 選取「焦點目標」分頁後,焦點會移至「第 1 張資訊卡」

8. 將焦點移至所選分頁

您可以指定鍵盤焦點進入焦點群組時的焦點目標。舉例來說,當使用者將焦點移至分頁列時,您可以將焦點移至所選分頁。

您可以按照下列步驟實作這項行為:

  1. 開啟 App.kt
  2. App 可組合函式中宣告 focusRequesters 值。
  3. 使用 remember 函式傳回的 FocusRequester 物件清單傳回值,來初始化 focusRequesters 值。傳回清單的長度應與 Screens.entries 的長度相同。
  4. 使用 focusRequester 修飾符修改「分頁」可組合函式,將 focusRequester 值的每個 FocusRequester 物件與 Tab 可組合函式建立關聯。
  5. 使用 focusProperties 修飾符和 focusGroup 修飾符修改 PrimaryTabRow 可組合函式。
  6. 將 lambda 傳遞至 focusProperties 修飾符,並將 enter 屬性與另一個 lambda 建立關聯。
  7. 從與 enter 屬性相關聯的 lambda 中,傳回以 focusRequesters 值中的 selectedTabIndex 值建立索引的 FocusRequester。

修改後的程式碼如下所示:

@Composable
fun App(
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current

    var selectedScreen by rememberSaveable { mutableStateOf(Screen.FocusTarget) }
    val selectedTabIndex = Screen.entries.indexOf(selectedScreen)
    val focusRequesters = remember {
        List(Screen.entries.size) { FocusRequester() }
    }

    Column(modifier = modifier) {
        PrimaryTabRow(
            selectedTabIndex = selectedTabIndex,
            modifier = Modifier
                .focusProperties {
                    enter = {
                        focusRequesters[selectedTabIndex]
                    }
                }
                .focusGroup()
        ) {
            Screen.entries.forEachIndexed { index, screen ->
                Tab(
                    selected = screen == selectedScreen,
                    onClick = { selectedScreen = screen },
                    text = { Text(stringResource(screen.title)) },
                    modifier = Modifier.focusRequester(focusRequester = focusRequesters[index])
                )
            }
        }
        when (selectedScreen) {
            Screen.FocusTarget -> {
                FocusTargetTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp),
                )
            }

            Screen.FocusTraversalOrder -> {
                FocusTraversalOrderTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp)
                )
            }

            Screen.FocusRestoration -> {
                FocusGroupTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp)
                )
            }
        }
    }
}

您可以使用 focusProperties 修飾符控制焦點移動方式。在傳遞至修飾符的 lambda 中,修改 FocusProperties。當使用者在已修改的 UI 元素獲得焦點時按下 Tab 鍵或方向鍵,系統會參照 FocusProperties 選擇焦點目標。

設定 enter 屬性時,系統會評估屬性所設的 lambda,並移至與評估 lambda 傳回的 FocusRequester 物件相關聯的 UI 元素。

開始執行

現在,當使用者將焦點移至分頁列時,鍵盤焦點會移至所選分頁。您可以按照下列步驟嘗試這項做法:

  1. 執行應用程式
  2. 選取「焦點群組」分頁標籤
  3. 使用 down 方向鍵,將焦點移至「第 1 張資訊卡」
  4. 使用 up 方向鍵移動焦點。

圖 13. 焦點會移至所選分頁。

9. 焦點還原

使用者希望在工作中斷時,能輕鬆恢復工作。焦點還原功能可讓您在中斷後恢復工作。焦點還原會將鍵盤焦點移至先前選取的 UI 元素。

焦點還原功能的典型用途是影片串流應用程式的主畫面。畫面上會顯示多個影片內容清單,例如某個類別的電影或電視節目的集數。使用者會瀏覽各個清單,尋找有趣的內容。有時,使用者會返回先前查看的清單,繼續瀏覽。有了焦點還原功能,使用者就能繼續瀏覽,而無須將鍵盤焦點移至清單中上次查看的項目。

focusRestorer 修飾符會將焦點還原至焦點群組

使用 focusRestorer 修飾符,可將焦點儲存及還原至焦點群組。焦點離開焦點群組時,焦點會儲存先前焦點項目的參照。然後,當焦點重新進入焦點群組時,焦點會還原為先前聚焦的項目。

將焦點還原功能整合到「焦點群組」分頁

範例應用程式的「焦點群組」分頁有一個資料列包含「第 2 張卡」、「第 3 張卡」和「第 4 張卡」

GIF 動畫,顯示即使先前已將焦點放在第 3 張卡,但鍵盤焦點仍會從第 1 張卡移至第 2 張卡。

圖 14. 焦點群組包含「第 2 張卡」、「第 3 張卡」和「第 4 張卡」

您可以按照下列步驟,在資料列中整合焦點還原功能:

  1. 開啟「tab.FocusGroupTab.kt
  2. 使用 focusRestorer 修飾符修改 FocusGroupTab 可組合函式中的 Row 可組合函式。應先呼叫修飾符,再呼叫 focusGroup 修飾符。

修改後的程式碼如下所示:

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            modifier = Modifier
                .focusRestorer()
                .focusGroup(),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

開始執行

這時「焦點群組」分頁中的資料列會還原焦點,您可以按照下列步驟進行測試:

  1. 選取「焦點群組」分頁標籤
  2. 將焦點移至第 1 張資訊卡
  3. 使用 Tab 鍵將焦點移至「第 4 張資訊卡」
  4. 使用 up 方向鍵將焦點移至「第 1 張資訊卡」
  5. 按下 Tab

鍵盤焦點會移至「第 4 張資訊卡」,因為 focusRestorer 修飾符會儲存資訊卡的參照,並在鍵盤焦點進入設為資料列的焦點群組時還原焦點。

GIF 動畫,顯示鍵盤焦點重新進入某一資料列時,鍵盤焦點會移至該列中先前選取的資訊卡。

圖 15. 按下向上方向鍵後,焦點會返回「第 4 張資訊卡」,接著按下 Tab 鍵。

10. 編寫測試

您可以使用測試來測試已實作的鍵盤焦點管理功能。Compose 提供的 API 可用於測試 UI 元素是否獲得焦點,並在 UI 元件上執行按鍵操作。詳情請參閱「在 Jetpack Compose 中測試」程式碼實驗室。

測試「焦點目標」分頁

您在上一節修改了 FocusTargetTab 可組合函式,將「第 2 張資訊卡」設為焦點目標。針對您在上一節手動執行的實作項目編寫測試。您可以按照下列步驟編寫測試:

  1. 開啟 FocusTargetTabTest.kt。您將在後續步驟中修改 testSecondCardIsFocusTarget 函式。
  2. 針對「第 1 張資訊卡」,在 SemanticsNodeInteraction 物件上呼叫 requestFocus 方法,要求焦點移至「第 1 張資訊卡」
  3. 確認「第 1 張資訊卡」已使用 assertIsFocused() 方法獲得焦點。
  4. 呼叫 pressKey 方法,並在 lambda 中傳遞 Key.Tab 值至 performKeyInput 方法,即可執行 Tab 按鍵動作。
  5. 針對「第 2 張資訊卡」,在 SemanticsNodeInteraction 物件上呼叫 assertIsFocused() 方法,測試鍵盤焦點是否會移至「第 2 張資訊卡」

更新後的程式碼如下所示:

@OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
@Test
fun testSecondCardIsFocusTarget() {
    composeTestRule.setContent {
        LocalInputModeManager
            .current
            .requestInputMode(InputMode.Keyboard)
        FocusTargetTab(onClick = {})
    }
    val context = InstrumentationRegistry.getInstrumentation().targetContext

    // Ensure the 1st card is focused
    composeTestRule
        .onNodeWithText(context.getString(R.string.first_card))
        .requestFocus()
        .performKeyInput { pressKey(Key.Tab) }

    // Test if focus moves to the 2nd card from the 1st card with Tab key
    composeTestRule
        .onNodeWithText(context.getString(R.string.second_card))
        .assertIsFocused()
}

開始執行

您可以按一下 FocusTargetTest 類別宣告左側的三角形圖示,執行測試。詳情請參閱「在 Android Studio 中測試」一文的「執行測試」一節。

Android Studio 顯示內容選單,用於執行「FocusTargetTabTest」。

11. 恭喜

非常好! 您已瞭解鍵盤焦點管理的構成元素:

  • 焦點目標
  • 焦點遍歷

您可以使用下列 Compose 修飾符控制焦點遍歷順序:

  • focusGroup 修飾符
  • focusProperties 修飾符

您已實作使用硬體鍵盤的典型使用者體驗模式、初期焦點和焦點還原功能。這些模式是透過結合下列 API 來實作:

  • FocusRequester 類別
  • focusRequester 修飾符
  • focusRestorer 修飾符
  • LaunchedEffect 可組合函式

您可以使用檢測設備測試來測試已實作的使用者體驗。Compose 提供執行按鍵操作的方法,並測試 SemanticsNode 是否有鍵盤焦點。

瞭解詳情