Gérer le curseur du clavier dans Compose

1. Introduction

Les utilisateurs peuvent interagir avec votre application à l'aide d'un clavier physique, généralement sur des appareils à grand écran tels que les tablettes et les appareils ChromeOS, mais aussi sur des appareils XR. Il est important que les utilisateurs puissent naviguer dans votre application aussi efficacement avec un clavier physique qu'avec un écran tactile. De plus, lorsque vous concevez votre application pour les écrans de TV ou de voiture, qui ne disposent pas forcément d'une saisie tactile et qui reposent plutôt sur des pavés directionnels ou des encodeurs rotatifs, vous devez appliquer les mêmes principes de navigation au clavier.

Compose vous permet de gérer les saisies provenant de claviers physiques, de pavés directionnels et d'encodeurs rotatifs de manière unifiée. Un principe clé d'une bonne expérience utilisateur pour ces modes de saisie est que les utilisateurs peuvent déplacer de manière intuitive et cohérente le focus clavier vers le composant interactif avec lequel ils souhaitent interagir.

Dans cet atelier de programmation, vous allez apprendre les points suivants :

  • Comment implémenter des modèles courants de gestion du focus clavier pour une navigation intuitive et cohérente
  • Comment vérifier si le déplacement du focus clavier se comporte comme prévu

Prérequis

  • Expérience dan la création d'applications avec Compose.
  • Connaissances de base de Kotlin, y compris des lambdas et des coroutines

Objectif de l'atelier

Vous implémentez les modèles de gestion du focus clavier suivants :

  • Mouvement du focus clavier : du début à la fin, de haut en bas dans le schéma en forme de Z
  • Focus initial logique : sélectionnez l'élément d'interface utilisateur avec lequel l'utilisateur est susceptible d'interagir
  • Restauration du focus : déplacez le focus sur l'élément d'interface utilisateur avec lequel l'utilisateur a interagi précédemment

Points abordés

  • Principes de base de la gestion du focus dans Compose
  • Comment créer un élément d'interface utilisateur en tant que cible de focus
  • Comment demander à ce que le focus déplace un élément d'interface utilisateur
  • Comment déplacer le focus clavier vers un élément d'interface utilisateur spécifique dans un groupe d'éléments d'interface utilisateur

Ce dont vous avez besoin

  • Android Studio Ladybug ou version ultérieure
  • L'un des appareils suivants pour exécuter l'application exemple :
  • Un appareil à grand écran doté d'un clavier physique
  • Un appareil virtuel Android pour les appareils à grand écran, tels que l'émulateur redimensionnable

2. Configuration

  1. Clonez le dépôt GitHub contenant les ateliers de programmation propres aux grands écrans :
git clone https://github.com/android/large-screen-codelabs

Vous pouvez également télécharger et désarchiver le fichier ZIP "large-screen-codelabs" :

  1. Accédez au dossier focus-management-in-compose :
  2. Dans Android Studio, ouvrez le projet. Le dossier focus-management-in-compose contient un projet.
  3. Si vous ne disposez pas d'une tablette Android, d'un appareil pliable ou d'un appareil ChromeOS équipé d'un clavier physique, ouvrez le Device Manager (Gestionnaire d'appareils) dans Android Studio, puis créez l'appareil Resizable (Redimensionnable) dans la catégorie Phone (Téléphone).

Le Gestionnaire d'appareils d'Android Studio affiche la liste des appareils virtuels disponibles dans la catégorie "Téléphone". L'émulateur redimensionnable fait partie de cette catégorie.Figure 1. Configuration de l'émulateur redimensionnable dans Android Studio.

3. Explorer le code de démarrage

Le projet comporte deux modules :

  • start : contient le code de démarrage du projet. Vous allez modifier ce code pour terminer l'atelier de programmation.
  • solution : contient le code final pour cet atelier de programmation.

L'application exemple comporte trois onglets :

  • Focus target (Cible de focus)
  • Focus traversal order (Ordre de balayage focus)
  • Focus group (Groupe focus)

L'onglet de la cible de focus s'affiche lorsque l'application est lancée.

Le premier affichage de l'application exemple. Elle comporte trois onglets et l'onglet cible du focus, le premier, qui est sélectionné. L'onglet affiche trois cartes placées dans une colonne.

Figure 2. L'onglet Focus target (Cible de focus) s'affiche au lancement de l'application.

Le package ui contient le code d'interface utilisateur suivant avec lequel vous interagissez :

4. Focus target (Cible de focus)

Une cible de focus est un élément d'interface utilisateur sur lequel le focus clavier peut se déplacer. Les utilisateurs peuvent déplacer le focus clavier à l'aide de la touche Tab ou des touches directionnelles (fléchées) :

  • Touche Tab : le focus se déplace vers la cible suivante ou précédente de façon unidimensionnelle.
  • Touches directionnelles : le focus peut se déplacer de façon bidimensionnelle : vers le haut, le bas, la gauche et la droite.

Les onglets sont des cibles de focus. Dans l'application exemple, l'arrière-plan des onglets est mis à jour visuellement lorsque le focus est sur l'onglet.

Le fichier d'animation GIF montre comment le focus clavier se déplace entre les différents éléments de l'interface utilisateur. Il se déplace sur les trois onglets, puis la première carte est sélectionnée.

Figure 3. L'arrière-plan du composant change lorsque le focus se déplace vers l'une de ses cibles.

Les éléments d'interface utilisateur interactifs sont des cibles de focus par défaut

Un composant interactif est une cible de focus par défaut. En d'autres termes, l'élément d'interface utilisateur est une cible de focus si les utilisateurs peuvent appuyer dessus.

L'application exemple comporte trois cartes dans l'onglet Focus target (Cible de focus). Les 1er et 3e éléments sont des cibles de focus. Le 2e élément ne l'est pas. L'arrière-plan de la 3e carte est mis à jour lorsque l'utilisateur déplace le focus depuis la 1re carte avec la touche Tab.

L'animation GIF montre le mouvement initial du focus clavier dans l'onglet "Focus target" (Cible de focus). Il ignore la deuxième carte et passe à la troisième à partir de la première lorsque l'utilisateur appuie sur la touche Tabulation sur la première carte.

Figure 4. Les cibles de focus de l'application excluent la 2e carte.

Modifier la deuxième carte pour qu'elle soit une cible de focus

Vous pouvez définir la 2e carte comme cible de focus en la transformant en élément d'interface utilisateur interactif. Le moyen le plus simple consiste à utiliser le modificateur clickable comme suit :

  1. Ouvrez FocusTargetTab.kt dans le package tabs.
  2. Modifiez le composable SecondCard avec le modificateur clickable comme suit :
@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)
        )
    }
}

Exécuter l'application

L'utilisateur peut désormais sélectionner la 2e carte en plus de la 1re carte et de la 3e carte. Vous pouvez essayer sur l'onglet Focus target (Cible de focus). Vérifiez que vous pouvez déplacer le focus de la 1re carte vers la 2e carte à l'aide de la touche Tab.

L'animation GIF montre le mouvement du focus clavier après la modification. Lorsque le focus est sur la première carte et que l'utilisateur appuie sur la touche Tabulation, le focus se déplace en partant de la première carte.

Figure 5. Déplacement du focus de la 1re carte vers la 2e carte à l'aide de la touche Tab

5. Parcours du focus dans un schéma en forme de Z

Les utilisateurs s'attendent à ce que le focus clavier se déplace de gauche à droite et de haut en bas si les paramètres linguistiques sont configurés pour un système d'écriture de gauche à droite. Cet ordre de balayage est appelé schéma en forme de Z.

Toutefois, Compose ignore la mise en page lorsqu'il détermine la prochaine cible de focus de la touche Tab et utilise à la place un balayage unidimensionnel basé sur l'ordre des appels de fonction composable.

Balayage du focus unidimensionnel

L'ordre de balayage du focus unidimensionnel provient de l'ordre des appels de fonction composable plutôt que de la mise en page de l'application.

Dans l'application exemple, le focus se déplace dans l'ordre suivant dans l'onglet Focus traversal order (Ordre de balayage du focus) :

  1. 1re carte
  2. 4e carte
  3. 3e carte
  4. 2e carte

L'animation GIF montre que le focus clavier se déplace différemment par rapport aux attentes de l'utilisateur.  Il passe de la 1re à la 3e carte, puis à la 4e et à la 2e. Cela peut être différent des attentes de l'utilisateur.

Figure 6. Le balayage du focus suit l'ordre des fonctions composables.

La fonction FocusTraversalOrderTab implémente l'Focus traversal order (Ordre de balayage du focus) de l'application exemple. La fonction appelle les fonctions conposables pour les cartes : FirstCard, FourthCard, ThirdCard et SecondCard, dans cet ordre.

@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)
        )
    }
}

Mouvement du focus dans le schéma en forme de Z

Vous pouvez intégrer le mouvement du focus en forme de Z dans l'onglet Focus traversal order (Ordre de balayage du focus) de l'application exemple en procédant comme suit :

  1. Ouvrir tabs.FocusTraversalOrderTab.kt
  2. Supprimez le modificateur de décalage des composables ThirdCard et FourthCard.
  3. Remplacez la disposition actuelle de l'onglet (une ligne avec deux colonnes) par une disposition à une colonne avec deux lignes.
  4. Déplacez les composables FirstCard et SecondCard vers la première ligne.
  5. Déplacez les composables ThirdCard et FourthCard vers la deuxième ligne.

Le code modifié est le suivant :

@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)
            )
        }
    }
}

Exécuter l'application

L'utilisateur peut désormais déplacer le focus de droite à gauche et de haut en bas, selon le schéma en forme de Z. Vous pouvez essayer dans l'onglet Focus traversal order (Ordre de balayage du focus) et vérifier que le focus se déplace dans l'ordre suivant à l'aide de la touche Tab 

  1. 1re carte
  2. 2e carte
  3. 3e carte
  4. 4e carte

L'animation GIF montre comment le focus clavier se déplace après la modification. Il se déplace de gauche à droite, de haut en bas, en suivant une forme de Z.

Figure 7. Balayage du focus en forme de Z.

6. focusGroup

Le focus passe de la 1re carte à la 3e carte à l'aide de la touche directionnelle right de l'onglet Focus group (Groupe focus). Ce mouvement est probablement un peu déroutant pour les utilisateurs, car les deux cartes ne sont pas côte à côte.

L'animation GIF montre que le focus clavier passe de la première carte à la troisième à l'aide de la touche de direction droite. Ces deux cartes sont placées sur des lignes différentes.

Figure 8. Déplacement inattendu du focus de la 1re carte à la 3e.

Le balayage du focus bidimensionnel fait référence aux informations de mise en page

Appuyer sur une touche directionnelle déclenche un balayage du focus à deux dimensions. Il s'agit d'un déplacement de focus courant sur les téléviseurs, car les utilisateurs interagissent avec votre application à l'aide d'un pavé directionnel. Appuyer sur les touches fléchées du clavier déclenche également un balayage à deux dimensions, car elles imitent la navigation avec un pavé directionnel.

Dans le balayage du focus bidimensionnel, le système fait référence aux informations géométriques des éléments de l'interface utilisateur et détermine la cible de focus pour le déplacement. Par exemple, le focus se déplace vers la 1re carte à partir de l'onglet de la cible du focus avec la touche directionnelle down. Si vous appuyez sur la touche directionnelle vers le haut, le focus se déplace vers l'onglet Focus target (Cible de focus)

Le GIF montre que le focus passe à la première carte à partir de l'onglet "Focus target" (cible du focus) avec la touche vers le bas, puis revient à l'onglet avec la touche vers le haut. Ces deux cibles du focus sont les plus proches verticalement.

Figure 9. Balayage du focus avec les touches de direction vers le bas et vers le haut.

Le balayage du focus bidimensionnel ne se termine pas, contrairement au déplacement du focus unidimensionnel avec la touche Tab. Par exemple, l'utilisateur ne peut pas déplacer le focus avec la touche vers le bas lorsque la 2e carte est sélectionnée.

Le GIF montre que le focus reste sur la deuxième carte, même si l'utilisateur appuie sur la touche de direction vers le bas, car aucune cible n'est placée sous la carte.

Figure 10. La touche directionnelle vers le bas ne peut pas déplacer le focus lorsque le focus est sur la deuxième carte.

Cibles focus au même niveau

Le code suivant implémente l'écran mentionné plus haut. Il existe quatre cibles de focus : FirstCard, SecondCard, ThirdCard et FourthCard. Ces quatre cibles du focus sont au même niveau, et ThirdCard est le premier élément à droite de FirstCard dans la mise en page. C'est pourquoi le focus passe de la 1re carte à la 3e carte avec la touche directionnelle right.

@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)
            )
        }
    }
}

Grouper les cibles de focus avec le modificateur focusGroup

Vous pouvez modifier le mouvement déroutant du focus en procédant comme suit :

  1. Ouvrir tabs.FocusGroup.kt
  2. Modifiez la fonction composable Column dans la fonction composable FocusGroupTab avec le modificateur focusGroup.

Le code mis à jour est le suivant :

@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)
            )
        }
    }
}

Le modificateur focusGroup crée un groupe de focus composé des cibles de focus dans le composant modifié. Les cibles de focus dans le groupe de focus et celles en dehors de ce groupe sont à des niveaux différents. Aucune cible de focus n'est placée à droite du composable FirstCard. Par conséquent, le focus ne passe pas de la 1re carte à une autre carte lors d'un appui sur la touche directionnelle right.

Exécuter l'application

Désormais, le focus ne passe pas de la 1re carte à la 3e carte avec la touche directionnelle right dans l'onglet Focus group (Groupe focus) de l'application exemple.

7. Demander le focus

Les utilisateurs ne peuvent pas utiliser de clavier ni de pavé directionnel pour sélectionner des éléments d'interface utilisateur arbitraires avec lesquels interagir. Les utilisateurs doivent déplacer le focus clavier vers un composant interactif avant d'interagir avec l'élément.

Par exemple, les utilisateurs doivent déplacer le focus de l'onglet Focus target (Cible de focus) vers la 1re carte avant d'interagir avec celle-ci. Vous pouvez réduire le nombre d'actions à effectuer pour lancer la tâche principale de l'utilisateur en définissant logiquement le focus initial.

L'animation GIF montre que l'utilisateur doit appuyer trois fois sur la touche de tabulation après avoir sélectionné l'onglet pour déplacer le focus clavier vers la première carte de l'onglet.

Figure 11. Trois appuis sur la touche Tab permettent de sélectionner la 1re carte.

Demander le focus avec FocusRequester

Vous pouvez demander à ce que le focus déplace un élément d'interface utilisateur avec FocusRequester. Un objet FocusRequester doit être associé à un élément d'interface utilisateur avant d'appeler la méthode requestFocus().

Définir le focus initial sur la première carte

Vous pouvez définir le focus initial sur la 1re carte en procédant comme suit :

  1. Ouvrir tabs.FocusTarget.kt
  2. Déclarez la valeur firstCard dans la fonction composable FocusTargetTab et initialisez-la avec un objet FocusRequester renvoyé par la fonction remember.
  3. Modifiez la fonction composable FirstCard avec le modificateur focusRequester.
  4. Spécifiez la valeur firstCard comme argument du modificateur focusRequester.
  5. Appelez la fonction composable LaunchedEffect avec la valeur Unit, puis appelez la méthode requestFocus() sur la valeur firstCard dans le lambda transmis à la fonction composable LaunchedEffect.

Un objet FocusRequester est créé et associé à un élément d'interface utilisateur à la deuxième et à la troisième étape. À la cinquième étape, il est demandé au focus de se déplacer sur l'élément d'interface utilisateur associé lorsque le composable FocusdTargetTab est composé pour la première fois.

Le code mis à jour se présente comme suit :

@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()
    }
}

Exécuter l'application

Désormais, le focus clavier se place sur la 1re carte de l'onglet Focus target (Cible de focus) lorsque l'onglet est sélectionné. Vous pouvez essayer en changeant d'onglet. De plus, la 1re carte est sélectionnée au lancement de l'application.

L'animation GIF montre que le focus clavier se déplace automatiquement vers la première carte lorsque l'utilisateur sélectionne l'onglet "Focus target" (Cible de focus).

Figure 12. Le focus est placé sur la 1re carte lorsque l'onglet Focus target (Cible de focus) est sélectionné.

8. Placer le focus sur l'onglet sélectionné

Vous pouvez spécifier la cible de focus lorsque le focus clavier entre dans un groupe de focus. Par exemple, vous pouvez placer le focus sur l'onglet sélectionné lorsque l'utilisateur place le focus sur la ligne d'onglets.

Pour implémenter ce comportement, procédez comme suit :

  1. Ouvrez App.kt.
  2. Déclarez la valeur focusRequesters dans la fonction composable App.
  3. Initialisez la valeur focusRequesters avec la valeur renvoyée par la fonction remember, qui renvoie une liste d'objets FocusRequester. La longueur de la liste renvoyée doit être égale à celle de Screens.entries.
  4. Associez chaque objet FocusRequester de la valeur focusRequester au composable Tab en modifiant le composable "Tab" avec le modificateur focusRequester.
  5. Modifiez le composable "PrimaryTabRow" avec le modificateur focusProperties et le modificateur focusGroup.
  6. Transmettez un lambda au modificateur focusProperties et associez la propriété onEnter à un autre lambda.
  7. Appelez la méthode requestFocus sur le "FocusRequester", qui est indexé avec la valeur selectedTabIndex dans la valeur focusRequesters, à partir du lambda associé à la propriété enter.

Le code modifié se présente comme suit :

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

    val backstack = rememberNavBackStack(Tab.FocusTarget)
    val selectedTabIndex = Tab.entries.indexOf(backstack.last())
    val focusRequesters = remember {
        List(Tab.entries.size) { FocusRequester() }
    }

    Column(modifier = modifier) {
        PrimaryTabRow(
            selectedTabIndex = selectedTabIndex,
            modifier = Modifier
                .focusProperties {
                    onEnter = {
                        focusRequesters[selectedTabIndex].requestFocus()
                    }
                }
                .focusGroup()
        ) {
            Tab.entries.forEachIndexed { index, tab ->
                val isSelected = selectedTabIndex == index
                Tab(
                    selected = isSelected,
                    onClick = {
                        if (!isSelected) {
                            backstack.add(tab)
                        }
                    },
                    text = { Text(stringResource(tab.title)) },
                    modifier = Modifier.focusRequester(focusRequester = focusRequesters[index])
                )
            }
        }
        NavDisplay(
            backStack = backstack,
            entryProvider = entryProvider {
                entry<Tab.FocusTarget> {
                    FocusTargetTab(
                        onClick = context::onCardClicked,
                        modifier = Modifier.padding(32.dp),
                    )
                }
                entry<Tab.FocusTraversalOrder> {
                    FocusTraversalOrderTab(
                        onClick = context::onCardClicked,
                        modifier = Modifier.padding(32.dp)
                    )
                }
                entry<Tab.FocusGroup> {
                    FocusGroupTab(
                        onClick = context::onCardClicked,
                        modifier = Modifier.padding(32.dp)
                    )
                }
            }
        )
    }
}

Vous pouvez contrôler le mouvement du focus avec le modificateur focusProperties. Dans le lambda transmis au modificateur, modifiez "FocusProperties", qui est référencé lorsque le système choisit la cible de focus lorsque les utilisateurs appuient sur la touche Tab ou sur les touches directionnelles lorsque l'élément d'interface utilisateur modifié est sélectionné.

Lorsque le focus entre dans un groupe focus, le système appelle le lambda défini sur la propriété onEnter. Vous pouvez déplacer le focus vers un élément d'interface utilisateur en demandant le focus dans le lambda.

Exécuter l'application

Désormais, le focus clavier se place sur l'onglet sélectionné lorsque l'utilisateur met le focus sur la ligne d'onglets. Pour ce faire, procédez comme suit :

  1. Exécuter l'application
  2. Sélectionnez l'onglet Focus group (Groupe de focus).
  3. Déplacez le focus sur la 1re carte à l'aide de la touche directionnelle down.
  4. Déplacez le focus à l'aide de la touche directionnelle up.

Figure 13. Le focus se place sur l'onglet sélectionné.

9. Restauration du focus

Les utilisateurs s'attendent à pouvoir reprendre facilement une tâche lorsqu'elle est interrompue. La restauration du focus permet de reprendre après une interruption. La restauration du focus déplace le focus clavier vers l'élément d'interface utilisateur précédemment sélectionné.

L'écran d'accueil des applications de streaming vidéo est un cas d'utilisation typique de la restauration du focus. L'écran affiche plusieurs listes de contenus vidéo, comme des films d'une certaine catégorie ou des épisodes d'un programme TV. Les utilisateurs parcourent les listes et trouvent des contenus intéressants. Parfois, les utilisateurs reviennent à la liste précédemment consultée et continuent de la parcourir. Grâce à la restauration du focus, les utilisateurs peuvent continuer à parcourir la liste sans que le focus clavier ne revienne sur le dernier élément consulté.

Le modificateur "focusRestorer" restaure le focus sur un groupe de focus

Utilisez le modificateur focusRestorer pour enregistrer et restaurer le focus d'un groupe de focus. Lorsque le focus quitte le groupe de focus, il stocke une référence à l'élément précédemment sélectionné. Lorsque le focus retourne dans le groupe de focus, il revient sur l'élément précédemment sélectionné.

Intégrer la restauration du focus avec l'onglet "Focus group" (Groupe de focus)

L'onglet Focus group (Groupe de focus) de l'application exemple comporte une ligne contenant les éléments 2nd card (2e carte), 3rd card (3e carte) et 4th card (4e carte).

L&#39;animation GIF montre que le focus clavier passe de la première carte à la deuxième, même si la troisième carte était sélectionnée auparavant.

Figure 14. Groupe de focus contenant la 2e carte, la 3e carte et la 4e carte.

Vous pouvez intégrer la restauration du focus dans la ligne en procédant comme suit :

  1. Ouvrir tab.FocusGroupTab.kt
  2. Modifier le composable Row dans le composable FocusGroupTab avec le modificateur focusRestorer. Ce modificateur doit être appelé avant le modificateur focusGroup.

Le code modifié se présente comme suit :

@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)
            )
        }
    }
}

Exécuter l'application

La ligne de l'onglet Focus group (Groupe de focus) est à nouveau sélectionnée. Vous pouvez essayer de la sélectionner en procédant comme suit :

  1. Sélectionnez l'onglet Focus group (Groupe de focus).
  2. Déplacez le focus sur la 1re carte.
  3. Placez le focus sur la 4e carte à l'aide de la touche Tab.
  4. Déplacez le focus sur la 1re carte avec la touche directionnelle up.
  5. Appuyez sur la touche Tab.

Le focus clavier se déplace vers la 4e carte, car le modificateur focusRestorer enregistre la référence de la carte et rétablit le focus lorsque le focus clavier entre dans le groupe de focus défini sur la ligne.

L&#39;animation GIF montre que le focus clavier se déplace vers la carte précédemment sélectionnée dans une ligne lorsque le focus clavier y est à nouveau placé.

Figure 15. Le focus revient à la 4e carte après un appui sur la touche directionnelle vers le haut, suivi d'un appui sur la touche Tab.

10. Écrire un test

Vous pouvez tester la gestion du focus clavier implémentée avec des tests. Compose fournit une API permettant de vérifier si le focus est sur élément d'interface utilisateur, et aussi d'effectuer des appuis sur les touches des composants de l'interface utilisateur. Pour en savoir plus, consultez l'atelier de programmation Tester dans Jetpack Compose.

Tester l'onglet "Focus target" (Cible de focus)

Dans la section précédente, vous avez modifié la fonction composable FocusTargetTab pour définir la deuxième carte comme cible du focus. Créez un test pour l'implémentation que vous avez effectuée manuellement dans la section précédente. Le test peut être écrit en suivant les étapes ci-dessous :

  1. Ouvrez FocusTargetTabTest.kt. Vous allez modifier la fonction testSecondCardIsFocusTarget lors des prochaines étapes.
  2. Demandez à ce que le focus se déplace vers la 1re carte en appelant la méthode requestFocus sur l'objet SemanticsNodeInteraction pour la 1re carte.
  3. Assurez-vous que la 1re carte est sélectionnée avec la méthode assertIsFocused().
  4. Appuyez sur la touche Tab en appelant la méthode pressKey avec la valeur Key.Tab à l'intérieur du lambda transmis à la méthode performKeyInput.
  5. Vérifiez si le focus clavier se déplace vers la 2e carte en appelant la méthode assertIsFocused() sur l'objet SemanticsNodeInteraction pour la 2e carte.

Le code mis à jour se présente comme suit :

@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()
}

Exécuter l'application

Vous pouvez exécuter le test en cliquant sur l'icône en forme de triangle affichée à gauche de la déclaration de la classe FocusTargetTest. Pour en savoir plus, consultez la section Run tests (Exécuter des tests) de l'article Test in Android Studio (Tester dans Android Studio).

Android Studio affiche un menu contextuel pour exécuter &quot;FocusTargetTabTest&quot;.

11. Félicitations

Bravo ! Vous avez appris les principes de base de la gestion du focus clavier:

  • Focus target (Cible de focus)
  • Balayage du focus

Vous pouvez contrôler l'ordre de balayage du focus à l'aide des modificateurs Compose suivants :

  • Le modificateur focusGroup
  • Le modificateur focusProperties

Vous avez implémenté le modèle typique de l'expérience utilisateur avec un clavier physique, le focus initial et la restauration du focus. Ces modèles sont implémentés en combinant les API suivantes :

  • Classe FocusRequester
  • Le modificateur focusRequester
  • Le modificateur focusRestorer
  • Fonction composable LaunchedEffect

L'expérience utilisateur implémentée peut être testée avec des tests d'instrumentation. Compose permet d'appuyer sur les touches et de vérifier si un SemanticsNode a le focus clavier ou non.

En savoir plus