Il est parfois nécessaire d'ignorer le comportement de sélection par défaut des éléments de votre écran. Par exemple, vous pouvez regrouper des composables, empêcher la sélection d'un certain composable, demander explicitement le ciblage sur un composable donné, capturer ou libérer le focus, ou rediriger le curseur lors de l'entrée ou de la sortie. Cette section explique comment modifier le comportement de sélection lorsque les valeurs par défaut ne vous conviennent pas.
Proposez une navigation cohérente avec les groupes d'étude
Parfois, Jetpack Compose ne devine pas immédiatement l'élément suivant correct pour la navigation par onglets, en particulier lorsque des Composables
parents complexes tels que des onglets et des listes entrent en jeu.
Bien que la recherche ciblée suit généralement l'ordre de déclaration de Composables
, cela est impossible dans certains cas, par exemple lorsque l'un des Composables
de la hiérarchie est un élément à défilement horizontal qui n'est pas entièrement visible. Ce processus est illustré dans l'exemple ci-dessous.
Jetpack Compose peut décider de placer le curseur sur l'élément suivant le plus proche du début de l'écran, comme indiqué ci-dessous, plutôt que de suivre le chemin attendu pour la navigation dans un sens:
Dans cet exemple, il est clair que les développeurs n'avaient pas l'intention de passer de l'onglet Chocolats à la première image ci-dessous, puis de revenir à l'onglet Pastries. Au lieu de cela, il souhaitait se concentrer sur les onglets jusqu'au dernier, puis sur le contenu interne:
Dans les cas où il est important qu'un groupe de composables obtienne le focus de manière séquentielle, comme dans la ligne de tabulation de l'exemple précédent, vous devez encapsuler le Composable
dans un parent disposant du modificateur focusGroup()
:
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
La navigation bidirectionnelle recherche le composable le plus proche dans la direction donnée. Si un élément d'un autre groupe est plus proche qu'un élément non entièrement visible du groupe actuel, la navigation sélectionne l'élément le plus proche. Pour éviter ce comportement, vous pouvez appliquer le modificateur focusGroup()
.
FocusGroup
fait apparaître un groupe entier comme une seule entité en termes de ciblage, mais le groupe lui-même ne reçoit pas le ciblage, mais l'enfant le plus proche obtient le focus. De cette manière, la navigation sait qu'il doit accéder à l'élément non entièrement visible avant de quitter le groupe.
Dans ce cas, les trois instances de FilterChip
sont ciblées avant les éléments SweetsCard
, même si les SweetsCards
sont entièrement visibles par l'utilisateur et que certaines FilterChip
peuvent être masquées. En effet, le modificateur focusGroup
indique au gestionnaire de focus d'ajuster l'ordre dans lequel les éléments sont sélectionnés afin que la navigation soit plus facile et plus cohérente avec l'interface utilisateur.
Sans le modificateur focusGroup
, si FilterChipC
n'était pas visible, la navigation de sélection la sélectionne en dernier. Cependant, l'ajout d'un tel modificateur le rend non seulement visible, mais permet également d'acquérir l'attention des utilisateurs juste après FilterChipB
, comme s'y attendent les utilisateurs.
Rendre un composable sélectionnable
Certains composables peuvent être sélectionnés par conception, tels qu'un bouton ou un composable auquel le modificateur clickable
est associé. Si vous souhaitez ajouter spécifiquement un comportement sélectionnable à un composable, utilisez le modificateur focusable
:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
Rendre un composable non sélectionnable
Dans certains cas, certains de vos éléments ne devraient pas participer à la mise au point. Dans ces rares cas, vous pouvez utiliser canFocus property
pour exclure une Composable
de la sélection.
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
Demander la sélection au clavier avec FocusRequester
Dans certains cas, vous pouvez demander explicitement la sélection en réponse à une interaction utilisateur. Par exemple, vous pouvez demander à un utilisateur s'il souhaite recommencer à remplir un formulaire. S'il appuie sur "Oui", vous souhaitez alors recentrer le premier champ de ce formulaire.
La première chose à faire est d'associer un objet FocusRequester
au composable vers lequel vous souhaitez déplacer le focus du clavier. Dans l'extrait de code suivant, un objet FocusRequester
est associé à un TextField
en définissant un modificateur appelé Modifier.focusRequester
:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
Vous pouvez appeler la méthode requestFocus
de FocusRequester pour envoyer des requêtes de sélection réelles. Vous devez appeler cette méthode en dehors d'un contexte Composable
(dans le cas contraire, elle est réexécutée à chaque recomposition). L'extrait de code suivant montre comment demander au système de déplacer la sélection au clavier lorsque l'utilisateur clique sur le bouton:
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") }
Capturer et relâcher la mise au point
Vous pouvez tirer parti de la sélection pour guider vos utilisateurs afin qu'ils fournissent les données dont votre application a besoin pour effectuer sa tâche (par exemple, obtenir une adresse e-mail ou un numéro de téléphone valides). Bien que les états d'erreur informent vos utilisateurs de ce qui se passe, vous aurez peut-être besoin que le champ contenant des informations erronées reste sélectionné jusqu'à ce qu'il soit corrigé.
Pour capturer le focus, vous pouvez appeler la méthode captureFocus()
, puis la libérer par la suite avec la méthode freeFocus()
, comme dans l'exemple suivant:
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
Priorité des modificateurs de sélection
Les Modifiers
peuvent être considérés comme des éléments qui n'ont qu'un seul enfant. Par conséquent, lorsque vous les mettez en file d'attente, chaque Modifier
à gauche (ou en haut) encapsule l'élément Modifier
qui suit à droite (ou en dessous). Cela signifie que la deuxième Modifier
est contenue dans la première. Ainsi, lorsque vous déclarez deux focusProperties
, seul le plus haut fonctionne, car les suivants sont contenus dans le plus haut.
Pour mieux comprendre le concept, consultez le code suivant:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Dans ce cas, le focusProperties
indiquant item2
comme focus droit ne sera pas utilisé, car il est contenu dans le précédent. Par conséquent, item1
sera celui utilisé.
En utilisant cette approche, un parent peut également réinitialiser le comportement par défaut à l'aide de FocusRequester.Default
:
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Le parent ne doit pas nécessairement faire partie de la même chaîne de modificateurs. Un composable parent peut écraser une propriété de focus d'un composable enfant. Prenons l'exemple de ce FancyButton
qui rend le bouton non sélectionnable:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
Un utilisateur peut rendre ce bouton sélectionnable en définissant canFocus
sur true
:
FancyButton(Modifier.focusProperties { canFocus = true })
Comme toutes les Modifier
, celles liées à la sélection se comportent différemment en fonction de l'ordre dans lequel vous les déclarez. Par exemple, le code suivant rend le Box
sélectionnable, mais FocusRequester
n'est pas associé à ce composant sélectionnable, car il est déclaré après celui-ci.
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
N'oubliez pas qu'un focusRequester
est associé au premier élément sélectionnable en dessous dans la hiérarchie. Ainsi, focusRequester
pointe vers le premier enfant sélectionnable. Si aucun d'eux n'est disponible, il ne pointe vers rien.
Toutefois, comme Box
peut être sélectionné (grâce au modificateur focusable()
), vous pouvez y accéder en utilisant la navigation bidirectionnelle.
Un autre exemple peut également fonctionner, car le modificateur onFocusChanged()
fait référence au premier élément sélectionnable qui apparaît après les modificateurs focusable()
ou focusTarget()
.
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
Rediriger le curseur à l'entrée ou à la sortie
Parfois, vous devez fournir un type de navigation très spécifique, comme celui illustré dans l'animation ci-dessous:
Avant de plonger dans le processus de création, il est important de comprendre le comportement par défaut de la recherche ciblée. Sans modification, une fois que la recherche ciblée atteint l'élément Clickable 3
, appuyer sur DOWN
sur le pavé directionnel (ou sur la touche fléchée équivalente) déplace le curseur sur l'élément affiché sous Column
, quitte le groupe et ignore celui de droite. Si aucun élément sélectionnable n'est disponible, le focus ne se déplace pas, mais reste sur Clickable 3
.
Pour modifier ce comportement et fournir la navigation prévue, vous pouvez utiliser le modificateur focusProperties
, qui vous aide à gérer ce qui se passe lorsque la recherche ciblée entre dans le Composable
ou la quitte:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
Il est possible de diriger le curseur vers un élément Composable
spécifique chaque fois qu'il entre dans une certaine partie de la hiérarchie ou qu'il en sort (par exemple, lorsque votre UI comporte deux colonnes et que vous souhaitez vous assurer que, chaque fois que la première est traitée, le curseur passe à la seconde) :
Dans cet GIF, une fois que le curseur atteint la Clickable 3 Composable
dans Column
1, l'élément suivant sélectionné est Clickable 4
dans un autre Column
. Ce comportement peut être obtenu en combinant les valeurs focusDirection
avec les valeurs enter
et exit
dans le modificateur focusProperties
. Ils ont tous deux besoin d'un lambda qui prend comme paramètre la direction de départ et renvoie un FocusRequester
. Ce lambda peut se comporter de trois manières différentes: le renvoi de FocusRequester.Cancel
empêche la sélection de poursuivre, tandis que FocusRequester.Default
ne modifie pas son comportement. Si vous indiquez à la place le FocusRequester
associé à un autre Composable
, le curseur se déplace sur cette Composable
spécifique.
Modifier la direction de la progression de la mise au point
Pour avancer le curseur sur l'élément suivant ou vers une direction précise, vous pouvez exploiter le modificateur onPreviewKey
et impliquer le LocalFocusManager
pour avancer le curseur avec le modificateur moveFocus
.
L'exemple suivant montre le comportement par défaut du mécanisme de sélection: lorsqu'une touche tab
est détectée, le curseur passe à l'élément suivant dans la liste de sélection. Bien que cette configuration ne soit généralement pas nécessaire, il est important de connaître le fonctionnement interne du système pour pouvoir modifier le comportement par défaut.
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 } } )
Dans cet exemple, la fonction focusManager.moveFocus()
fait passer le curseur sur l'élément spécifié ou sur la direction implicite dans le paramètre de la fonction.
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Réagir à la concentration
- Sélection dans Compose
- Modifier l'ordre de balayage du curseur