Modifier le comportement de mise au point

Il est parfois nécessaire de remplacer le comportement de sélection par défaut des éléments à l'écran. Par exemple, vous pouvez regrouper des composables, empêcher sélectionner un composable donné, demander explicitement la sélection sur un composable, capturez ou relâchez le focus, ou redirigez le focus à l'entrée ou à la sortie. Ce explique comment modifier le comportement de sélection lorsque les valeurs par défaut ne correspondent pas à besoin.

Assurer une navigation cohérente au sein des groupes d'étude

Parfois, Jetpack Compose ne détermine pas immédiatement l'élément suivant la navigation par onglets, surtout lorsque les Composables parents complexes, comme les onglets et les listes entrent en jeu.

Alors 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 dans le est un défilement horizontal qui n'est pas entièrement visible. Ceci est illustré dans l'exemple ci-dessous.

Jetpack Compose peut décider de sélectionner l'élément suivant le plus proche du début de à l'écran, comme indiqué ci-dessous, plutôt que de poursuivre sur le chemin attendu pour navigation unidirectionnelle:

Animation d'une application montrant une navigation horizontale en haut et une liste d'éléments en dessous.
Figure 1. Animation d'une application montrant une barre de navigation horizontale en haut et une liste d'éléments en dessous

Dans cet exemple, il est clair que les développeurs n'avaient pas l'intention de se concentrer passez de l'onglet Chocolats à la première image ci-dessous, puis revenez à la Onglet Pâtisseries. Au lieu de cela, il voulait se concentrer sur les onglets jusqu'à ce que le dernier onglet, puis concentrez-vous sur le contenu interne:

Animation d'une application montrant une navigation horizontale en haut et une liste d'éléments en dessous.
Figure 2. Animation d'une application montrant une barre de navigation horizontale en haut et une liste d'éléments en dessous

Dans les cas où il est important qu'un groupe de composables gagne la priorité séquentiellement, comme dans la ligne de tabulation de l'exemple précédent, vous devez encapsuler le Composable dans un parent ayant le 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 pour l'élément donné direction (si un élément d'un autre groupe est plus proche d'un élément non entièrement visible) dans le groupe actuel, la navigation choisit l'élément le plus proche. Pour éviter cela, vous pouvez appliquer le modificateur focusGroup().

FocusGroup fait apparaître un groupe entier comme une seule entité en termes d'attention. mais le groupe lui-même ne reçoit pas l'attention. Au lieu de cela, l'enfant le plus proche se concentrer à la place. Ainsi, la navigation sait qu'il faut accéder élément avant de quitter le groupe.

Dans ce cas, les trois instances de FilterChip seront sélectionnées avant la SweetsCard éléments, même si les SweetsCards sont entièrement visibles par le utilisateur et certains FilterChip sont peut-être masqués. Cela se produit parce que le Le modificateur focusGroup indique au gestionnaire de sélection d'ajuster l'ordre dans lequel les éléments 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, sélectionner la navigation la retiendrait en dernier. Cependant, l'ajout d'un tel modificateur seulement visible, mais elle sera également mise en avant juste après FilterChipB, comme s'attendent à ce que les utilisateurs.

Rendre un composable sélectionnable

Certains composables sont sélectionnables à la conception, comme un bouton ou un composable avec le modificateur clickable qui lui est associé. Si vous souhaitez ajouter spécifiquement 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

Il peut y avoir des situations dans lesquelles certains de vos éléments ne devraient pas participer au premier plan. Dans ces rares cas, vous pouvez profiter du canFocus property pour empêcher le ciblage d'un Composable.

var checked by remember { mutableStateOf(false) }

Switch(
    checked = checked,
    onCheckedChange = { checked = it },
    // Prevent component from being focused
    modifier = Modifier
        .focusProperties { canFocus = false }
)

Demander le focus au clavier avec FocusRequester

Dans certains cas, vous pouvez demander explicitement la concentration en réponse à une l'interaction de l'utilisateur. Par exemple, vous pouvez demander à un utilisateur s'il souhaite redémarrer qui remplissent un formulaire, et s'ils appuient sur « oui » vous devez recentrer le premier champ sous cette forme.

La première chose à faire est d'associer un objet FocusRequester au composable sur lequel vous souhaitez sélectionner le clavier. Dans le code suivant, extrait, 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. (sinon, elle est réexécutée à chaque recomposition). L'extrait suivant montre comment demander au système de déplacer le curseur du clavier lorsque le bouton est cliqué:

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 exploiter la concentration pour guider vos utilisateurs afin qu'ils fournissent les données appropriées pour votre application. doit mener à bien sa tâche, par exemple, obtenir une adresse e-mail ou un numéro de téléphone valides numéro. Les états d'erreur informent vos utilisateurs de ce qui se passe, peut avoir besoin d'un champ contenant des informations erronées pour rester concentré jusqu'à ce qu'il soit ou corriger un problème.

Pour capturer le focus, vous pouvez appeler la méthode captureFocus(), puis libérez-le ensuite avec la méthode freeFocus() à la place, comme dans l'exemple Exemple:

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 mise au point

Modifiers peuvent être considérés comme des éléments qui n'ont qu'un seul enfant. Ainsi, lorsque vous mettez en file d'attente chaque Modifier à gauche (ou en haut) encapsule la Modifier qui suit vers la droite (ou en dessous). Cela signifie que le second Modifier est contenu à l'intérieur le premier. Ainsi, lorsque vous déclarez deux focusProperties, seul le premier car celles-ci sont contenues dans la première.

Pour clarifier le concept, consultez le code suivant:

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

Dans ce cas, le focusProperties indiquant item2 comme le focus droit sera ne doit pas être utilisé, car il est contenu dans l'énoncé précédent ; item1 sera donc utilisée.

Grâce à cette approche, un parent peut également rétablir 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 modificateur. Un parent Un composable peut écraser la propriété de focus d'un composable enfant. Par exemple : Prenons l'exemple 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") }
    }
}

Pour activer à nouveau ce bouton, définissez canFocus sur true:

FancyButton(Modifier.focusProperties { canFocus = true })

Comme chaque Modifier, les actions axées sur un élément se comportent différemment selon l'ordre. lorsque vous les déclarez. Par exemple, un code comme celui-ci rend Box sélectionnable, mais FocusRequester n'est pas associé à celui-ci, car il est déclarée après l'élément sélectionnable.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

N'oubliez pas qu'un focusRequester est associé au premier sélectionnable en dessous de lui dans la hiérarchie, donc ce focusRequester pointe vers premier enfant sélectionnable. Si aucun n'est disponible, il ne pointera vers rien. Toutefois, comme Box est sélectionnable (grâce au modificateur focusable()), vous pouvez y accéder en utilisant la navigation bidirectionnelle.

Autre exemple : l'une ou l'autre des méthodes suivantes fonctionne, car onFocusChanged() qui 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 comme illustré dans l'animation ci-dessous:

Animation d'un écran montrant deux colonnes de boutons placés côte à côte et une animation d'une colonne à l'autre.
Figure 3. Animation d'un écran montrant deux colonnes de boutons placés côte à côte et une animation d'une colonne à l'autre

Avant d'entrer dans les détails, il est important de comprendre le comportement de la recherche ciblée. Sans aucune modification, une fois que la recherche de focus atteint l'élément Clickable 3 en appuyant sur DOWN sur le pavé directionnel (ou l'équivalent flèche) permet de déplacer le curseur vers l'élément affiché sous Column, en quittant le groupe et en ignorant celui de droite. S'il n'y a pas de éléments sélectionnables disponibles, le curseur ne bouge pas, mais reste activé Clickable 3

Pour modifier ce comportement et fournir la navigation prévue, vous pouvez utiliser le Le modificateur focusProperties, qui vous aide à gérer ce qui se passe lorsque la sélection la recherche entre ou quitte le Composable:

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 ou sort d'une certaine partie de la hiérarchie, par exemple, lorsque votre UI comporte deux et vous voulez vous assurer qu'à chaque fois que la première est traitée, le focus passe à la seconde:

Animation d'un écran montrant deux colonnes de boutons placés côte à côte et une animation d'une colonne à l'autre.
Figure 4. Animation d'un écran montrant deux colonnes de boutons placés côte à côte et une animation d'une colonne à l'autre

Dans ce GIF, une fois que le curseur a atteint le Clickable 3 Composable dans Column 1, l'élément suivant en cours de sélection est Clickable 4 dans un autre Column. Ce comportement Vous pouvez obtenir ce résultat en combinant focusDirection avec enter et exit. dans le modificateur focusProperties. Ils ont tous les deux besoin d'un lambda qui prend comme paramètre la direction d'où provient le focus et renvoie une FocusRequester Ce lambda peut se comporter de trois manières différentes: renvoyer FocusRequester.Cancel empêche la mise au point de continuer, tandis que FocusRequester.Default ne modifie pas son comportement. En fournissant FocusRequester attaché à un autre Composable fait passer le curseur sur celle-ci des Composable spécifiques.

Modifier la direction d'avance de la mise au point

Pour passer à l'élément suivant ou dans une direction précise, vous pouvez utilisez le modificateur onPreviewKey et impliquez LocalFocusManager pour déplacez le curseur avec le modificateur moveFocus.

L'exemple suivant illustre le comportement par défaut du mécanisme de focus: lorsqu'un Une pression de touche tab est détectée, le curseur passe à l'élément suivant. liste. Bien que cela ne soit pas quelque chose que vous devez généralement configurer, il est important de connaître le fonctionnement interne du système afin de pouvoir modifier comportemental.

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() déplace le curseur vers l'élément spécifié, ou dans la direction implicite dans le paramètre de fonction.