1. はじめに
ユーザーはハードウェア キーボードを使用してアプリを操作できます。通常はタブレットや ChromeOS デバイスなどの大画面デバイスで使用しますが、XR デバイスでも利用できます。ユーザーがハードウェア キーボードでもタッチスクリーンと同じように、快適にアプリを操作できるようにすることが重要です。また、タップ入力ではなく D-pad やロータリー エンコーダを使用する可能性のあるテレビや車載ディスプレイ向けにアプリを設計する場合も、同様のキーボード ナビゲーションの原則を適用する必要があります。
Compose を使用すると、ハードウェア キーボード、D-pad、ロータリー エンコーダからの入力を統一された方法で処理できます。これらの入力方法で優れたユーザー エクスペリエンスを実現するための重要な原則は、ユーザーが操作するインタラクティブなコンポーネントにキーボード フォーカスを直感的かつ一貫して移動できることです。
この Codelab では、次のことを学びます。
- 直感的で一貫したナビゲーションを実現するための一般的なキーボード フォーカスの管理パターンを実装する方法
- キーボード フォーカスの移動が想定どおりに動作するかどうかをテストする方法
前提条件
- Compose を使ってアプリを構築した経験
- Kotlin に関する基本的な知識(ラムダやコルーチンなど)
構築内容
次のような一般的なキーボード フォーカスの管理パターンを実装します。
- キーボード フォーカスの移動 - 開始から終了まで、上から下に Z 字型のパターンで移動する
- 論理的な当初のフォーカス - ユーザーが操作する可能性が高い UI 要素にフォーカスを設定する
- フォーカスの復元 - ユーザーが以前操作した UI 要素にフォーカスを移動する
学習内容
- Compose でのフォーカス管理の基本
- UI 要素をフォーカス ターゲットにする方法
- UI 要素にフォーカスを移動するリクエストの方法
- グループ内の特定の UI 要素にキーボード フォーカスを移動する方法
必要なもの
- Android Studio Ladybug 以降
- サンプルアプリを実行する次のいずれかのデバイス:
- ハードウェア キーボードを搭載した大画面デバイス
- 大画面デバイス用の Android 仮想デバイス(サイズ変更可能なエミュレータなど)
2. 設定
- large-screen-codelabs の GitHub リポジトリのクローンを作成します。
git clone https://github.com/android/large-screen-codelabs
または、large-screen-codelabs の ZIP ファイルをダウンロードしてアーカイブを解除します。
focus-management-in-compose
フォルダに移動します。- Android Studio でプロジェクトを開きます。
focus-management-in-compose
フォルダには 1 つのプロジェクトが含まれています。 - Android タブレット、折りたたみ式デバイス、ハードウェア キーボードを搭載した ChromeOS デバイスがない場合は、Android Studio で [デバイス マネージャー] を開き、[Phone] カテゴリで [Resizable] デバイスを作成します。
図 1. Android Studio でサイズ変更可能なエミュレータを設定します。
3. スターター コードを確認する
このプロジェクトには 2 つのモジュールがあります。
- start - プロジェクトのスターター コードが含まれています。このコードに変更を加えて Codelab を完了します。
- solution - この Codelab の完成したコードが含まれています。
サンプルアプリは次の 3 つのタブで構成されています。
- フォーカス ターゲット
- フォーカス移動順序
- フォーカス グループ
アプリの起動時に、[フォーカス ターゲット] タブが表示されます。
図 2. アプリの起動時に [フォーカス ターゲット] タブが表示されます。
ui
パッケージには、操作する以下の UI コードが含まれています。
App.kt
- タブを実装しますtab.FocusTargetTab.kt
- [フォーカス ターゲット] タブのコードが含まれますtab.FocusTraversalOrderTab.kt
- [フォーカス移動順序] タブのコードが含まれますtab.FocusGroup.kt
- [フォーカス グループ] タブのコードが含まれますFocusGroupTabTest.kt
-tab.FocusTargetTab.kt
のインストルメンテーション テスト(ファイルはandroidTest
フォルダにあります)
4. フォーカス ターゲット
フォーカス ターゲットは、キーボード フォーカスを移動できる UI 要素です。ユーザーは Tab
キーまたは方向キー(矢印キー)を使用してキーボード フォーカスを移動できます。
Tab
キー - フォーカスは次のフォーカス ターゲットまたは前のフォーカス ターゲットに 1 次元で移動します。- 方向キー - フォーカスは 2 次元(上、下、左、右)に移動できます。
タブはフォーカス ターゲットです。サンプルアプリでは、タブがフォーカスを取得すると、タブの背景が視覚的に更新されます。
図 3. フォーカスがフォーカス ターゲットに移動すると、コンポーネントの背景が変化します。
インタラクティブな UI 要素はデフォルトでフォーカス ターゲットになる
インタラクティブ コンポーネントは、デフォルトでフォーカス ターゲットです。つまり、ユーザーがタップできる UI 要素はフォーカス ターゲットであるということです。
サンプルアプリの [フォーカス ターゲット] タブには 3 つのカードがあります。1 番目と3 番目のカードはフォーカス ターゲットですが、2 番目のカードはフォーカス ターゲットではありません。ユーザーが Tab
キーで 1 番目のカードから 3 番目のカードにフォーカスを移動すると、3 番目のカードの背景が更新されます。
図 4. アプリのフォーカス ターゲットには2 番目のカードは含まれません。
2 番目のカードをフォーカス ターゲットに変更する
2 番目のカードをフォーカス ターゲットにするには、インタラクティブな UI 要素に変更します。最も簡単な方法は、次のように clickable
修飾子を使用することです。
tabs
パッケージのFocusTargetTab.kt
を開きます。- 次のように、
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 番目のカードにフォーカスを移動できることを確認します。
図 5. Tab
キーを使用して、1 番目のカードから 2 番目のカードにフォーカスを移動します。
5. Z 字型パターンのフォーカス走査
左から右の言語設定では、ユーザーはキーボード フォーカスが左から右、上から下に移動することを想定しています。このフォーカス移動順序を「Z 字型パターン」と呼びます。
ただし、Compose はレイアウトを無視して、Tab
キーの次のフォーカス ターゲットを決定します。代わりにコンポーズ可能な関数の呼び出し順序に基づいて 1 次元のフォーカス走査を使用します。
1 次元のフォーカス走査
1 次元のフォーカス移動順序は、アプリのレイアウトではなく、コンポーズ可能な関数の呼び出し順序に基づきます。
サンプルアプリでは、[フォーカス移動順序] タブでフォーカスが次の順序で移動します。
- 1 番目のカード
- 4 番目のカード
- 3 番目のカード
- 2 番目のカード
図 6. フォーカス走査は、コンポーズ可能な関数の順序に従います。
FocusTraversalOrderTab
関数は、サンプルアプリのフォーカス走査タブを実装しています。この関数は、カードのコンポーズ可能な関数(FirstCard
、FourthCard
、ThirdCard
、SecondCard
)を順番に呼び出します。
@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 字型のフォーカス移動を統合できます。
tabs.FocusTraversalOrderTab.kt
を開くThirdCard
コンポーザブルとFourthCard
コンポーザブルからオフセット修飾子を削除する- タブのレイアウトを、現在の 2 列 1 行から 1 列 2 行に変更する
FirstCard
コンポーザブルとSecondCard
コンポーザブルを 1 行目に移動するThirdCard
コンポーザブルとFourthCard
コンポーザブルを 2 行目に移動する
変更後のコードは次のようになります。
@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 番目のカード
- 2 番目のカード
- 3 番目のカード
- 4 番目のカード
図 7. Z 字型パターンのフォーカス走査。
6. focusGroup
[フォーカス グループ] タブで right
方向キーを使用すると、フォーカスが 1 番目のカードから 3 番目のカードに移動します。2 つのカードが並んで表示されないため、ユーザーには少しわかりにくいかもしれません。
図 8. 1 番目のカードから 3 番目のカードへの予期しないフォーカス移動。
レイアウト情報に基づく 2 次元フォーカス走査
方向キーを押すと、2 次元のフォーカス走査がトリガーされます。これは、ユーザーが D-padを使用してアプリを操作するときに、テレビでよく見られるフォーカス走査です。キーボードの矢印キーを押すと、D-pad によるナビゲーションを模倣して 2 次元のフォーカス走査もトリガーされます。
2 次元のフォーカス走査では、システムは UI 要素の幾何学的情報を参照し、フォーカスを移動するフォーカス ターゲットを決定します。たとえば、[フォーカス ターゲット] タブからdown
方向キーを使用するとフォーカスが 1 番目のカードに移動し、上方向キーを押すとフォーカスが再び [フォーカス ターゲット] タブに移動します。
図 9. 下方向キーと上方向キーによるフォーカス走査。
2 次元フォーカス走査は、Tab
キーによる 1 次元フォーカス走査とは異なり、ラップアラウンドしません。たとえば、2 番目のカードにフォーカスが移動したときに、ユーザーは下方向キーでフォーカスを移動できません。
図 10. 2 番目のカードにフォーカスがあるときは、下方向キーではフォーカスを移動できません。
同じレベルにあるフォーカス ターゲット
次のコードは、上記の画面を実装しています。フォーカス ターゲットは FirstCard
、SecondCard
、ThirdCard
、FourthCard
の 4 つです。これらの 4 つのフォーカス ターゲットは同じレベルにあり、レイアウト上では 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 修飾子を使用してフォーカス ターゲットをグループ化する
フォーカスの動きがわかりにくい場合は、次の手順で変更できます。
tabs.FocusGroup.kt
を開くFocusGroupTab
コンポーズ可能な関数内で、focusGroup
修飾子を使用して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 番目のカードに移動する必要があります。当初のフォーカスを論理的に設定することで、ユーザーの主なタスクを開始するアクションの数を減らすことができます。
図 11. Tab
キーを 3 回押すと、フォーカスが 1 番目のカードに移動します。
FocusRequester でフォーカスをリクエストする
FocusRequester
を使用して、UI 要素にフォーカスを移動するようにリクエストできます。requestFocus()
メソッドを呼び出す前に、FocusRequester
オブジェクトを UI 要素に関連付ける必要があります。
1 番目のカードに当初のフォーカスを設定
当初のフォーカスを 1 番目のカードに設定する手順は次のとおりです。
tabs.FocusTarget.kt
を開く- コンポーズ可能な関数
FocusTargetTab
でfirstCard
値を宣言し、remember
関数から返されたFocusRequester
オブジェクトでその値を初期化する focusRequester
修飾子を使用してコンポーズ可能な関数FirstCard
を変更するfirstCard
値をfocusRequester
修飾子の引数として指定するUnit
値を指定してコンポーズ可能な関数LaunchedEffect
を呼び出す。LaunchedEffect
コンポーズ可能な関数に渡されたラムダ内でfirstCard
値に対して requestFocus() メソッドを呼び出す
FocusRequester
オブジェクトが作成され、2 番目と 3 番目のステップで UI 要素に関連付けられる5 番目のステップでは、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 番目のカードが選択されます。
図 12. [フォーカス ターゲット] タブを選択すると、フォーカスが 1 番目のカードに移動します。
8. 選択したタブにフォーカスを移動する
キーボード フォーカスがフォーカス グループに入るときにフォーカス ターゲットを指定できます。たとえば、ユーザーがタブ行にフォーカスを移動したときに、選択したタブにフォーカスを移動できます。
この動作を実装する手順は次のとおりです。
App.kt
を開きます。- コンポーズ可能な関数
App
でfocusRequesters
値を宣言します。 focusRequesters
値を初期化するには、FocusRequester
オブジェクトのリストを返すremember
関数の戻り値を使用します。返されるリストの長さはScreens.entries
の長さと同じにする必要があります。focusRequester
修飾子を使用して Tab コンポーザブルを変更し、focusRequester
値の各FocusRequester
オブジェクトをTab
コンポーザブルに関連付けます。focusProperties
修飾子とfocusGroup
修飾子を使用して、PrimaryTabRow コンポーザブルを変更します。- ラムダを
focusProperties
修飾子に渡し、enter
プロパティを別のラムダに関連付けます。 enter
プロパティに関連付けられたラムダから、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
修飾子で制御できます。修飾子に渡されるラムダで、FocusProperties を変更します。変更された UI 要素にフォーカスがある状態で、ユーザーが Tab
キーまたは方向キーを押すと、システムはフォーカス ターゲットを選択するために FocusProperties を参照します。
enter
プロパティを設定すると、システムはプロパティに設定されたラムダを評価します。その結果、ラムダから返された FocusRequester
オブジェクトに関連付けられている UI 要素にフォーカスを移動します。
実行する
これで、ユーザーがタブ行にフォーカスを移動すると、キーボード フォーカスが選択したタブに移動するようになりました。次の手順で試すことができます。
- アプリを実行する
- [フォーカス グループ] タブを選択する
down
方向キーを使用して、1 番目のカードにフォーカスを移動するup
方向キーでフォーカスを移動する
図 13. 選択したタブにフォーカスが移動します。
9. フォーカスの復元
ユーザーは、タスクが中断されたときに簡単に再開できることを期待しています。フォーカスの復元は、中断からの復旧をサポートします。フォーカスの復元では、以前に選択された UI 要素にキーボード フォーカスが移動します。
フォーカスの復元の一般的なユースケースは、動画ストリーミング アプリのホーム画面です。画面には、カテゴリ内のムービーやテレビ番組のエピソードなど、動画コンテンツの複数のリストが表示されます。ユーザーはリストを閲覧して、興味深いコンテンツを見つけます。以前に確認したリストに戻って引き続き閲覧することもあります。フォーカスの復元を使用すると、ユーザーはリストで最後に見たアイテムにキーボード フォーカスを移動せずに、引き続き閲覧できます。
focusRestorer 修飾子でフォーカスをフォーカス グループに復元
focusRestorer
修飾子を使用して、フォーカス グループにフォーカスを保存して復元します。フォーカスがフォーカス グループから離れると、フォーカスは以前にフォーカスされていたアイテムの参照を保存します。その後、フォーカスがフォーカス グループに戻ると、以前フォーカスされていたアイテムにフォーカスが復元されます。
フォーカスの復元をフォーカス グループタブに統合
サンプルアプリの [フォーカス グループ] タブには、2 番目のカード、3 番目のカード、4 番目のカードを含む行があります。
図 14. 2 番目のカード、3 番目のカード、4 番目のカードを含むフォーカス グループ。
次の手順で、フォーカスの復元を行に統合できます。
tab.FocusGroupTab.kt
を開くFocusGroupTab
コンポーザブルのRow
コンポーザブルをfocusRestorer
修飾子で変更します。修飾子は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 番目のカードにフォーカスを移動する
Tab
キーを押して 4 番目のカードにフォーカスを移動するup
方向キーを使用して 1 番目のカードにフォーカスを移動するTab
キーを押す
キーボード フォーカスは 4 番目のカードに移動します。これは、focusRestorer
修飾子がカードの参照を保存し、キーボード フォーカスが行に設定されたフォーカス グループに入るとフォーカスを復元するためです。
図 15. 上方向キーを押してから Tab
キーを押すと、フォーカスが 4 番目のカードに戻ります。
10. テストの作成
実装したキーボード フォーカス管理は、テストで確認できます。Compose には、UI 要素がフォーカスされているかどうかをテストし、UI コンポーネントでキー操作を行うための API が用意されています。詳細については、Jetpack Compose でのテストの Codelab をご覧ください。
フォーカス ターゲットタブのテスト
前のセクションでは、FocusTargetTab
コンポーズ可能な関数を変更して、2 番目のカードをフォーカス ターゲットとして設定しました。前のセクションで手動で行った実装のテストを作成します。テストは次の手順で記述できます。
FocusTargetTabTest.kt
を開きます。次の手順でtestSecondCardIsFocusTarget
関数を変更します。- 1 番目のカードの
SemanticsNodeInteraction
オブジェクトでrequestFocus
メソッドを呼び出し、フォーカスを 1 番目のカードに移動するようリクエストします。 assertIsFocused()
メソッドで 1 番目のカードがフォーカスされていることを確認します。performKeyInput
メソッドに渡されたラムダ内でKey.Tab
値を指定してpressKey
メソッドを呼び出し、Tab
キーを押します。- 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 でテストするのテストを実行するをご覧ください。
11. 完了
これで、キーボードのフォーカス管理の構成要素について学習できました。
- フォーカス ターゲット
- フォーカス走査
フォーカス移動順序は、次の Compose 修飾子で制御できます。
focusGroup
修飾子focusProperties
修飾子
ハードウェア キーボード、当初のフォーカス、フォーカスの復元を使用した UX の一般的なパターンを実装しました。これらのパターンは、次の API を組み合わせて実装できます。
FocusRequester
クラスfocusRequester
修飾子focusRestorer
修飾子LaunchedEffect
コンポーズ可能な関数
実装された UX は、インストルメンテーション テストで確認できます。Compose には、キー操作を実行し、SemanticsNode
にキーボード フォーカスがあるかどうかをテストする方法が用意されています。