Gestion des gestes: gestion des conflits de gestes. 3e partie

Une traduction de l'article a été préparée avant le lancement d'un cours de développement avancé et de base pour Android.



Il s'agit du troisième article d'une série sur le contrôle gestuel. Vous pouvez vous familiariser avec les traductions des première et deuxième parties si vous les avez manquées.

Dans les articles précédents, nous avons abordé le sujet du remplissage de l'espace d'écran d'un bord à l'autre. Dans cet article, nous verrons comment gérer les conflits qui surviennent entre les gestes de votre application et les nouveaux gestes du système dans Android 10 .

Qu'entendons-nous par conflits de gestes? Regardons un exemple. Disons que nous avons un lecteur de musique qui permet à l'utilisateur de faire défiler la chanson en cours par glisser-déposer SeekBar.



Malheureusement, il SeekBarest trop proche de la zone du geste de retour à l'écran d'accueil, à cause de quoi le geste de basculer rapidement vers l'application précédente (QuickSwitch) commence, ce qui gêne l'utilisateur.

La même chose peut se produire sur n'importe quel bord de l'écran où se trouvent les zones gestuelles. Il existe de nombreux exemples courants qui peuvent provoquer des conflits, tels que: tiroirs de navigation ( DrawerLayout), carrousels ( ViewPager ), curseurs ( SeekBar), glisser dans les listes.

Ce qui nous amène à la question "comment pouvons-nous résoudre ce problème?" Pour faciliter cette question, nous avons créé un organigramme qui vous propose une réponse en fonction de la situation.


Vous pouvez trouver une version PDF de l'organigramme ici .

J'espère que les questions ne nécessitent pas d'explication, mais juste au cas où, je passerai en revue chacune d'elles:

1. L'application doit-elle masquer les barres de navigation et d'état?


La première question est de savoir si le cas d'utilisation principal de votre application nécessite de masquer les barres de navigation et / ou d'état. Par masquage, nous voulons dire que ces panneaux système ne sont pas visibles du tout. Cela ne signifie pas que vous avez mis en œuvre le concept de «bord à bord» ou quelque chose de similaire dans votre application.

Raisons possibles pour répondre «oui» à cette question:


Des exemples courants d'applications qui devraient répondre «oui» à cette question sont les jeux, les lecteurs vidéo, les visionneuses de photos et les applications de dessin.

2. Le scénario principal d'utilisation de l'interface utilisateur implique des glissements dans / autour de la zone de geste?


Cette question permet de savoir si votre interface utilisateur contient des éléments dans / à côté des zones de gestes (à la fois «arrière» et «maison») que l'utilisateur doit balayer.

Les jeux disent généralement oui, car

  • Les commandes à l'écran sont généralement situées près du bord gauche / droit et du bas de l'écran.
  • Dans certains jeux, vous devez balayer les éléments qui peuvent se trouver n'importe où sur l'écran.

En plus des jeux, des exemples courants d'interfaces utilisateur qui devraient dire oui sont:

  • L'interface utilisateur recadre les photos où les cadres déplaçables sont également proches des bords gauche et droit de l'écran.
  • Une application de dessin où l'utilisateur peut dessiner sur une toile qui couvre tout l'écran.

3. Vue fréquemment utilisée dans / autour de la zone gestuelle?


J'espère que c'est une question assez simple. Cela inclut également les vues, qui couvrent la zone des gestes, puis s'étendent sur une grande partie de l'écran, par exemple, DrawerLayoutou sur une grande ViewPager.

4. La vue implique un glissement / glisser?


Nous changeons un peu de tactique et commençons à regarder les points de vue individuels. Pour tous les points de vue pour lesquels vous avez répondu par l'affirmative à la troisième question, nous apportons une petite précision: l'utilisateur doit-il glisser / glisser?

Il y a de nombreux exemples où vous devez répondre « oui»: SeekBars, BottomSheet ou même PopupMenu(vous devez glisser pour ouvrir).

5. La vue est-elle entièrement / généralement située sous les zones gestuelles?


Sur la base de la quatrième question, nous clarifions maintenant si la vue est entièrement ou principalement située dans la zone gestuelle.

Si votre vue se trouve dans un conteneur déroulant comme celui-ci RecyclerView, pensez à cette question un peu différemment: la vue entièrement / essentiellement développée tombe-t-elle sous la zone de mouvement dans toutes les positions de défilement ? Si l'utilisateur peut faire défiler la vue sous la zone des gestes, vous n'avez rien à faire.

Dans le diagramme ci-dessus, vous remarquerez peut-être un carrousel plein écran ( ViewPager) comme exemple de réponse négative et vous vous demandez pourquoi ce cas n'a pas besoin d'être traité. Cela est dû au fait que les zones de gestes gauche / droite sont relativement petites en largeur (par défaut: 20dpchacune) par rapport à la largeur de la vue. Typiquela largeur de l'écran de votre téléphone en orientation portrait est de ~ 360dp, laissant ~ 320dp d'espace libre dans lequel l'utilisateur n'a pas de difficulté (c'est presque 90% de l' écran). Même avec des marges / retraits internes, l'utilisateur peut toujours faire défiler confortablement le carrousel.

6. La bordure de vue chevauche-t-elle les zones de mouvement requises?


La dernière question précise si la vue se trouve sous l'une des zones de mouvement requises. Si vous vous souvenez de notre article précédent , vous vous souviendrez que les zones obligatoires des gestes du système sont des zones de l'écran où les gestes du système ont toujours la priorité.

Dans Android 10, il n'y a qu'une seule zone de geste obligatoire, située au bas de l'écran, qui permet à l'utilisateur de rentrer chez lui ou d'ouvrir ses dernières applications. Cela pourrait changer dans les futures versions de la plate-forme, mais maintenant nous n'avons plus qu'à travailler avec la vue en bas de l'écran.

Des exemples typiques sont:

  • Modeless BottomSheet , car ils ont tendance à se réduire en une petite vue glisser-déposer en bas de l'écran.
  • Un carrousel à défilement horizontal en bas de l'écran, par exemple, une interface avec des autocollants.

Maintenant que nous avons trié les questions, nous espérons que vous pourrez arriver à l'une des solutions, alors examinons chacune d'elles plus en détail.

Aucun conflit à gérer


Commençons par la «solution» la plus simple, ne faites rien !

Bien sûr, il y a peut-être encore de la place pour les optimisations que vous pouvez faire (voir la section ci-dessous), mais heureusement, il n'y a pas de problèmes sérieux en utilisant l'application avec le mode de navigation gestuelle activé.

Si l'horaire vous a amené ici, mais que vous pensez toujours qu'il y a un problème, veuillez nous en informer . Peut-être que nous avons manqué quelque chose.

Déplacement de la vue depuis les zones de mouvement


Comme nous l'avons appris dans notre article précédent , des encarts existent pour indiquer à votre application où se trouvent les zones de gestes du système à l'écran. L'une des méthodes que nous pouvons utiliser pour résoudre les conflits de mouvements consiste à déplacer toutes les vues conflictuelles des zones de mouvements. Ceci est particulièrement important pour la vue en bas de l'écran, car cette zone est la zone des gestes obligatoires et les applications ne peuvent pas y utiliser les API d'exclusion de gestes.

Prenons un autre regard sur un exemple. Nous avons une interface de lecteur de musique que nous avons montrée ci-dessus. Il contient SeekBar, situé en bas de l'écran, permettant à l'utilisateur de faire défiler la chanson.


Lecteur de musique UI avec une barre de recherche en bas de l'écran

Mais lorsqu'un utilisateur essaie de sauter une chanson, cela se produit:


Enregistrer un geste système en conflit avec SeekBar

En effet, la zone inférieure du geste chevauche la SeekBar, donc le geste de retour à la maison est prioritaire. Voici la visualisation des zones gestuelles:



Une solution simple


La solution la plus simple ici consiste à ajouter un retrait / marge supplémentaire afin que la barre de recherche se déplace vers le haut depuis la zone de mouvement. Quelque chose comme ceci:



si nous faisons glisser le SeekBar dans cet exemple, vous verrez que nous


n'activons plus le geste de retour: SeekBar n'entre plus en conflit avec le geste du système inférieur.

Pour l'implémenter, nous devons utiliser les nouveaux encarts de gestes système disponibles dans l'API 29 et la bibliothèque Jetpack Core v1.2.0 (actuellement en alpha ). Dans l'exemple, nous augmentons le retrait inférieur SeekBarpour correspondre à la valeur de l' encart inférieur du geste :

ViewCompat.setOnApplyWindowInsetsListener(seekBar) { view, insets ->
     //      view ,    system gesture insets
    view.updatePadding(
        bottom = insets.systemGestureInsets.bottom
    )
    insets
}

Si vous souhaitez apprendre à simplifier l'utilisation WindowInsets, vous pouvez lire notre autre article sur ce sujet:

WindowInsets - Disposition des écouteurs

Actions supplémentaires


À ce stade, vous pouvez décider que le travail est déjà terminé, et pour une mise en page, cela peut très bien être la solution finale. Mais dans notre exemple, l'interface utilisateur a régressé visuellement avec beaucoup d'espace perdu sous SeekBar. Ainsi, au lieu de simplement pousser la vue vers le haut, nous pouvons plutôt repenser la mise en page pour éviter de perdre de l'espace:


SeekBardéplacé vers le haut du panneau de lecture.

Ici, nous nous sommes déplacés SeekBarvers le haut du panneau de lecture, complètement en dehors de la zone des gestes. Cela signifie que nous n'avons plus besoin d'augmenter artificiellement la hauteur du panneau pour l'adapter SeekBar.

, . « ».

API


Dans notre précédent article, nous mentionnions que «les applications ont la possibilité d'exclure les mouvements du système pour certaines parties de l'écran» . Les applications le font à l'aide des API d'exclusion de gestes, qui sont apparues pour la première fois dans Android 10. Le

système propose deux fonctions différentes pour exclure les zones de gestes: View.setSystemGestureExclusionRects()et Window.setSystemGestureExclusionRects(). Ce que vous devez utiliser dépend de l'application: si vous utilisez Android View, le système préfère l'API de vue, sinon utilisez l' WindowAPI.

La principale différence entre les deux API est que l' API Windows'attend à ce que tous les rectangles soient dans l'espace de coordonnées de la fenêtre. Si vous utilisez la vue, vous travaillerez généralement dans l'espace de coordonnées de la vue. L'API View prend en charge la transformation entre les espaces de coordonnées, c'est-à-dire que vous devez raisonner uniquement en termes de contenu de la vue.

Regardons un exemple. Nous allons utiliser à nouveau notre exemple de lecteur de musique, qui SeekBarest situé sur toute la largeur de l'écran. Nous avons résolu le conflit SeekBaravec le geste de revenir à l'écran d'accueil dans la section précédente, mais nous avons encore des zones de gestes gauche et droite dont nous devons prendre soin.

Voyons ce qui se passe lorsqu'un utilisateur essaie de sauter une chanson lorsque le «curseur»SeekBar(glissement circulaire) est situé près de l'un des bords:


SeekBar a un conflit avec la zone de geste arrière.

Comme le curseur est sous la zone de geste droite, le système pense que l'utilisateur essaie de revenir en arrière en utilisant le geste, par conséquent, affiche la flèche arrière. Cela n'est pas pratique pour les utilisateurs, car ils ne voulaient probablement pas revenir en arrière pour le moment. Nous pouvons résoudre ce problème en utilisant les API d'exclusion de gestes mentionnées ci-dessus pour exclure les bordures du curseur.

Les API d'exception de geste sont généralement appelées à partir de deux endroits: onDraw()lorsque votre vue est rendue, et onLayout()autrement. Votre vue passeList<Rect>contenant tous les rectangles à exclure. Comme mentionné précédemment, ces rectangles doivent être dans leur propre système de coordonnées de vue.

Habituellement, vous créez une fonction comme celle-ci qui sera appelée depuis onLayout()et / ou onDraw():

private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
   //   ,      Android 10+
   if (Build.VERSION.SDK_INT < 29) return
  // -,     
   gestureExclusionRects.clear()
      //   ,    .  SeekBar    .
   thumb?.also { t ->
       gestureExclusionRects += t.copyBounds()
   }
   //            view,       ,     
   // ,       
   systemGestureExclusionRects = gestureExclusionRects
}

Un exemple complet peut être trouvé ici .

Après avoir ajouté cela, le rembobinage près des bords fonctionne comme prévu:


SeekBar fonctionne dans la zone de geste arrière

Remarque sur l'exemple ci-dessus. SeekBarle fait déjà automatiquement pour vous dans Android 10, il n'est donc pas nécessaire de le faire vous-même. Ici, nous le faisons juste à titre d'exemple pour vous montrer les grandes lignes.

Limites


Bien que les API d'exclusion de gestes puissent sembler être la solution parfaite pour résoudre tous les conflits de gestes, ce n'est en fait pas le cas. En utilisant les API d'exclusion de gestes, vous déclarez que le geste de votre application est plus important que les actions de retour du système. Ceci est une déclaration forte, donc cette API est conçue pour être une issue de secours lorsque vous ne pouvez rien faire d'autre.

En utilisant les API d'exclusion de gestes, vous déclarez que le geste de votre application est plus important que l'action système de retour en arrière.

Étant donné que le comportement fourni par l'API peut violer une expérience utilisateur confortable, le système limite son utilisation: les applications ne peuvent exclure que jusqu'à 200 dp par bord.

Voici quelques questions courantes que les développeurs se posent lorsqu'ils entendent cela:

Pourquoi la restriction est-elle nécessaire? J'espère que l'explication ci-dessus vous a déjà conduit à une raison. Nous pensons qu'il est très important pour l'utilisateur de pouvoir revenir en arrière de façon constante depuis le balayage latéral. De manière cohérente sur l'ensemble de l'appareil, pas une seule application. Cette restriction peut sembler trop restrictive, mais une seule application, à l'exclusion de tout le bord de l'écran, suffit pour gêner l'utilisateur, ce qui conduit soit à la suppression de l'application, soit à quelque chose de plus radical.

En d'autres termes, le système de navigation doit toujours être cohérent et facile à utiliser.

Pourquoi 200dp?L'argument pour 200dp est assez simple. Comme nous l'avons mentionné précédemment, les API d'exclusion de gestes sont destinées à être utilisées en dernier recours, donc cette limite a été calculée comme un multiple de plusieurs cibles tactiles importantes. La taille minimale recommandée pour une cible sensorielle est de 48dp.4 cibles sensorielles × 48dp = 192dp. Ajoutez un peu plus d'indentation et nous obtenons la valeur 200dp.

Que faire si j'ai besoin d'exclure plus de 200dp par front? Le système exclura uniquement les 200dp les plus bas que vous avez demandés.


Le système permet une demande pour une hauteur totale de 200 dp, à partir du bord inférieur de

Ma vue en dehors de l'écran, est-ce considéré comme une limite?Non, le système ne prend en compte que les rectangles exclus qui se trouvent à l'écran. De même, si la vue est partiellement à l'écran, seule la partie visible du rectangle demandé est prise en compte.

Plongez dans le prochain post


Peut-être qu'après avoir lu ceci, vous vous demandez pourquoi nous n'avons pas considéré le côté droit de l'organigramme. Les solutions ci-dessous sont conçues spécifiquement pour les applications qui utilisent tout l'écran dans le rendu. Nous en parlerons dans la prochaine partie.






All Articles