画面上の要素のデフォルト フォーカス動作をオーバーライドする必要がある場合があります。たとえば、コンポーザブルをグループ化する、特定のコンポーザブルにフォーカスできない、1 つのコンポーザブルに明示的にフォーカスをリクエストする、フォーカスを取得または解放する、開始時または終了時にフォーカスをリダイレクトする場合などが考えられます。このセクションでは、デフォルトが不要な場合にフォーカスの動作を変更する方法について説明します。
フォーカス グループで一貫したナビゲーションを提供する
Jetpack Compose は、タブ付きナビゲーションの次のアイテムをすぐに推測しないことがあります。特に、タブやリストなどの複雑な親の Composables
が関係する場合に顕著です。
フォーカス検索は通常、Composables
の宣言順序に従いますが、階層内の Composables
の 1 つが完全に表示されていない水平スクロール可能である場合など、これが不可能な場合もあります。以下に例を示します。
Jetpack Compose は、以下に示すように、一方向ナビゲーションで期待されるパスで続行するのではなく、画面の開始位置に最も近い次のアイテムをフォーカスすることを決定することがあります。
この例では、フォーカスが [Chocolates] タブから次の最初の画像に移動してから [Pastries] タブに戻る意図がなかったことがわかります。代わりに、最後のタブまでタブでフォーカスし、それから内部のコンテンツに集中するようにしました。
前の例のタブ行のように、コンポーザブルのグループが順番にフォーカスを取得する必要がある場合は、focusGroup()
修飾子を持つ親で Composable
をラップする必要があります。
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
双方向ナビゲーションは、指定された方向に最も近いコンポーザブルを探します。別のグループの要素が、現在のグループ内の完全に表示されていないアイテムよりも近くにある場合、ナビゲーションは最も近いコンポーザブルを選択します。この動作を回避するには、focusGroup()
修飾子を適用します。
FocusGroup
は、フォーカスの観点からグループ全体が単一のエンティティであるかのように見えますが、グループ自体がフォーカスを取得することはありません。代わりに、最も近い子がフォーカスを取得します。これにより、ナビゲーションは、グループから離れる前に、完全に表示されていないアイテムに移動することを認識します。
この場合、SweetsCards
がユーザーに完全に表示され、一部の FilterChip
が非表示になっている場合でも、FilterChip
の 3 つのインスタンスが SweetsCard
アイテムの前にフォーカスされます。これは、focusGroup
修飾子がフォーカス マネージャーに対して、ナビゲーションが簡単で、かつ UI との一貫性を高めるために、アイテムがフォーカスされる順序を調整するように指示するためです。
focusGroup
修飾子がない場合、FilterChipC
が表示されていない場合、フォーカス ナビゲーションはそれを最後に選択します。ただし、このような修飾子を追加すると、検出可能になるだけでなく、ユーザーの期待どおりに FilterChipB
の直後にフォーカスが取得されます。
コンポーザブルをフォーカス可能にする
一部のコンポーザブルは、ボタンや clickable
修飾子がアタッチされたコンポーザブルなど、設計上フォーカス可能です。コンポーザブルにフォーカス可能な動作を明示的に追加する場合は、focusable
修飾子を使用します。
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
コンポーザブルをフォーカス不可にする
一部の要素がフォーカスに参加すべきでない場合もあります。このようなまれなケースでは、canFocus property
を利用して Composable
をフォーカス可能から除外できます。
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
FocusRequester
でキーボード フォーカスをリクエストする
ユーザー操作に対するレスポンスとして、明示的にフォーカスをリクエストしたい場合があります。たとえば、フォームへの入力を再開するかどうかをユーザーに尋ね、ユーザーが「はい」を押した場合、そのフォームの最初のフィールドにフォーカスし直すことができます。
まず、キーボード フォーカスの移動先のコンポーザブルに FocusRequester
オブジェクトを関連付けます。次のコード スニペットでは、Modifier.focusRequester
という修飾子を設定して、FocusRequester
オブジェクトを TextField
に関連付けます。
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
FocusRequester の requestFocus
メソッドを呼び出して、実際のフォーカス リクエストを送信できます。このメソッドは、Composable
コンテキストの外部で呼び出す必要があります(そうしないと、再コンポーズのたびに再実行されます)。次のスニペットは、ボタンがクリックされたときにキーボード フォーカスを移動するようシステムにリクエストする方法を示しています。
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
フォーカスをキャプチャして解放
フォーカスを利用して、たとえば有効なメールアドレスや電話番号を取得するなど、アプリがタスクを実行するために必要な適切なデータをユーザーに提供するようユーザーに案内できます。エラー状態は、何が行われているかをユーザーに知らせますが、修正されるまでフォーカスを維持するために、誤った情報を含むフィールドが必要になることもあります。
フォーカスをキャプチャするには、次の例のように captureFocus()
メソッドを呼び出し、その後、代わりに freeFocus()
メソッドを使用して解放します。
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
フォーカス修飾子の優先順位
Modifiers
は、子が 1 つしかない要素と見なすことができます。キューに置くと、左側(または上)にある各 Modifier
が、右側(または下)に続く Modifier
をラップします。つまり、2 番目の Modifier
は最初のもの内に含まれるため、2 つの focusProperties
を宣言する場合、次のものは最上位に含まれるため、最上位にある 1 つのみが機能します。
コンセプトをより明確にするには、次のコードをご覧ください。
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
この場合、右フォーカスとして item2
を示す focusProperties
は、前のフォーカスに含まれているため使用されません。したがって、item1
が使用されます。
この方法を利用すると、親は FocusRequester.Default
を使用して動作をデフォルトにリセットすることもできます。
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
親は同じ修飾子チェーンの一部である必要はありません。親コンポーザブルは、子コンポーザブルのフォーカス プロパティを上書きできます。たとえば、ボタンをフォーカス不可にする次の FancyButton
について考えてみましょう。
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
ユーザーは、canFocus
を true
に設定することで、このボタンを再びフォーカス可能にすることができます。
FancyButton(Modifier.focusProperties { canFocus = true })
他の Modifier
と同様に、フォーカス関連オブジェクトは宣言する順序によって動作が異なります。たとえば、次のようなコードは Box
をフォーカス可能にしますが、FocusRequester
はフォーカス可能な後に宣言されるため、このフォーカス可能に関連付けられていません。
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
focusRequester
は、階層内でその下にある最初のフォーカス可能な子に関連付けられているため、この focusRequester
は最初のフォーカス可能な子を指していることに留意してください。利用できない場合は、何も指定されません。
ただし、Box
は(focusable()
修飾子により)フォーカス可能であるため、双方向ナビゲーションを使用して移動できます。
別の例として、onFocusChanged()
修飾子は focusable()
修飾子または focusTarget()
修飾子の後に表示される最初のフォーカス可能な要素を参照するため、次のいずれかを使用できます。
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
開始時または終了時にフォーカスをリダイレクトする
場合によっては、次のアニメーションに示すように、非常に特殊なナビゲーションを指定する必要があります。
作成方法の説明に入る前に、フォーカス検索のデフォルトの動作を理解しておくことが重要です。何も変更しない場合、フォーカス検索が Clickable 3
項目に到達した後、D-pad(または同等の矢印キー)で DOWN
を押すと、フォーカスが Column
の下に表示されているものに移動し、グループが終了して右側のものは無視されます。使用可能なフォーカス可能なアイテムがない場合、フォーカスはどこにも移動せず、Clickable 3
に留まります。
この動作を変更して目的のナビゲーションを提供するには、focusProperties
修飾子を利用します。これにより、フォーカス検索が Composable
に出入りするときの動作を管理できます。
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
階層の特定の部分に出入りするときにフォーカスを特定の Composable
に向けることができます。たとえば、UI に 2 つの列があり、最初の列が処理されるたびにフォーカスが 2 番目の列に切り替えられるようにする場合などです。
この GIF では、フォーカスが Column
1 の Clickable 3 Composable
に達すると、フォーカスされている次の項目は別の Column
の Clickable 4
になります。この動作を実現するには、focusProperties
修飾子内で focusDirection
と enter
および exit
の値を組み合わせる必要があります。どちらもフォーカスの方向をパラメータとして受け取り、FocusRequester
を返すラムダを必要とします。このラムダは、次の 3 つの方法で動作します。FocusRequester.Cancel
を返すとフォーカスの継続が停止しますが、FocusRequester.Default
はその動作を変更しません。代わりに、別の Composable
にアタッチされた FocusRequester
を指定すると、その特定の Composable
にフォーカスがジャンプします。
フォーカスの進行方向を変更
フォーカスを次のアイテムまたは正確な方向に進めるには、onPreviewKey
修飾子を使用し、moveFocus
修飾子でフォーカスを進めるように LocalFocusManager
を暗黙的に指定します。
次の例は、フォーカス メカニズムのデフォルトの動作を示しています。tab
キーの押下が検出されると、フォーカスはフォーカス リスト内の次の要素に進みます。これは通常、構成する必要があるものではありませんが、デフォルトの動作を変更できるようにするには、システムの内部動作を理解することが重要です。
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
このサンプルでは、focusManager.moveFocus()
関数が、指定されたアイテムに、または関数パラメータで暗黙的に指定された方向にフォーカスを進めます。
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- 集中して対応
- Compose のフォーカス
- フォーカス移動の順序を変更する