Principes de base des applications

Adapter les interactions de saisie au-delà de l'écran tactile avec Jetpack Compose

Haut-parleurs

  • Alex Vanyo

    Ingénieur logiciel

  • Jeremy Walker

    Ingénieur logiciel Compose

À propos

À propos de l'intervention

À mesure que de nouveaux facteurs de forme dépassent l'écran tactile, le système de saisie de Compose a évolué pour relever le défi. Son architecture en couches permet de prendre en charge de manière fluide aussi bien les gestes avancés sur le pavé tactile que les nouvelles entrées matérielles.

Grâce à des décompositions architecturales claires et à des exemples de code pratiques, suivez le cycle de vie d'un événement d'entrée, de la couche de la plate-forme de l'OS jusqu'aux détecteurs de gestes et aux composants d'interface utilisateur de Compose. Vous découvrirez comment tracer les événements dans l'ensemble de la pile, appliquer des modèles d'architecture clés pour les nouveaux types d'entrées et déboguer en toute confiance les interactions gestuelles complexes dans vos propres applications.

Résumé

  • Jetpack Compose traite les événements de pointeur Android entrants via un pipeline en six phases. Ce pipeline gère une arborescence de chemins d'accès aux hits et distribue les événements en trois passes sur les nœuds de l'interface utilisateur.
  • Les entrées du pavé tactile dans Compose sont désormais mappées aux événements de souris plutôt qu'aux événements tactiles. Compose convertit également les événements de faux doigt de la plate-forme en événements de pointeur de panoramique et de mise à l'échelle dédiés.
  • Les pavés tactiles des lunettes connectées introduisent des types de saisie indirects plutôt que des pressions sur les coordonnées de l'écran. Ils utilisent un axe de mouvement directionnel principal pour mapper les balayages horizontaux en navigation verticale vers l'élément suivant ou précédent.
  • La compatibilité avec les tests s'est étendue au-delà des événements tactiles avec de nouvelles méthodes telles que performTrackpadInput. Ces API fournissent des surfaces de test familières pour vérifier que les applications gèrent correctement les appareils non tactiles.

Gemini Notebook

Thèmes

  • jetpack-compose
  • pointer-input
  • pavés tactiles
  • gestes
  • intelligent-eyewear
  • accessibilité

Transcription

Introduction et philosophie de saisie

  • Hi, everyone!

  • Nous espérons que vous passez un bon moment au Sommet des développeurs Android, et nous vous remercions d'assister à notre conférence.

  • Je m'appelle Alex Vanyo et je suis ingénieur logiciel dans l'équipe Compose. Je suis accompagné de Jeremy, qui est également ingénieur logiciel dans l'équipe Compose.

  • Nous avons tous les deux travaillé à l'amélioration du système de saisie de Jetpack Compose. Aujourd'hui, nous allons vous expliquer comment nous l'avons étendu au-delà de l'écran tactile pour faciliter la prise en charge des saisies non tactiles sans configuration supplémentaire.

  • De manière générale, la saisie capture toutes les façons dont un utilisateur interagit avec votre application.

  • Les écrans tactiles sont probablement la première chose à laquelle vous pensez pour une application mobile, mais ils ne sont certainement pas le seul type de saisie.

  • Les autres types de saisie incluent les claviers physiques, les souris, les pavés tactiles, les stylets, les manettes et les dispositifs rotatifs.

  • Nous voyons également apparaître de nouveaux types de saisie, comme les pavés tactiles sur les lunettes connectées, le suivi des yeux et des mains en XR, et les mouvements des doigts et du poignet sur Wear.

  • Et si vous souhaitez aller encore plus loin, il existe d'autres types d'entrées, comme les IME logiciels pour la modification de texte, les micros, les caméras et d'autres capteurs d'appareil.

  • Vous devez vous attendre à ce que vos applications soient utilisées avec une grande variété de types d'entrées.

  • Cela s'explique notamment par la variété toujours croissante d'appareils sur lesquels les applications s'exécutent et par l'adaptabilité croissante de ces appareils.

  • Les facteurs de forme des appareils peuvent avoir une méthode de saisie plus courante, mais il est important de ne pas supposer qu'un facteur de forme d'appareil particulier implique l'utilisation d'un type de saisie particulier.

  • Bien qu'un Chromebook soit équipé d'un pavé tactile et d'un clavier intégrés, ces types d'entrée ne sont pas exclusifs à ce facteur de forme.

  • Vous devez vous attendre à ce que des claviers physiques, des pavés tactiles et des souris soient également utilisés sur les téléphones, les tablettes et les téléviseurs, et certains téléviseurs sont même équipés d'écrans tactiles.

  • L'accessibilité est une autre raison importante pour laquelle il faut s'attendre à plusieurs types de saisie et les prendre en charge.

  • Permettre aux utilisateurs d'utiliser le périphérique d'entrée de leur choix et de changer de périphérique d'entrée comme bon leur semble est la bonne chose à faire pour rendre les applications plus accessibles et utilisables.

  • Selon la juridiction, il peut être légalement obligatoire d'avoir un comportement spécifique et compatible dans votre application pour ces types d'entrée, en particulier pour les claviers et les souris.

  • En résumé, vous devez assurer la compatibilité avec différents types d'entrées, quel que soit le facteur de forme.

  • Voyons comment Jetpack Compose aide vos applications à répondre à ces besoins des utilisateurs.

  • La philosophie de Compose, en général, est que ces différents types d'entrée doivent se comporter de manière appropriée avec les composants standards, sans que vous n'ayez à faire quoi que ce soit de particulier.

  • Ensuite, vous devez disposer des outils nécessaires pour personnaliser et ajuster le comportement comme vous le souhaitez.

  • Au-delà de ces types d'entrée initiaux, à mesure que d'autres types d'entrée seront pris en charge par la plate-forme sous-jacente, nous prévoyons de les intégrer aux composants Compose, ce qui améliorera le comportement de vos applications simplement en mettant à jour votre version de Compose.

  • Dans cette présentation, nous allons examiner deux exemples de ce type, qui ont tous deux été ajoutés récemment à Compose.

  • Il se trouve que les deux sont des appareils de type pavé tactile, mais comme vous le verrez, ils sont utilisés de manière très différente.

  • Commençons par quelques travaux récents sur les pavés tactiles des ordinateurs portables.

  • Un pavé tactile est un périphérique d'entrée basé sur un pointeur.

  • Plus précisément, lorsque vous utilisez un pavé tactile, vous obtenez des coordonnées bidimensionnelles.

  • Mais contrairement à un écran tactile, les coordonnées des doigts sur le pavé tactile ne se trouvent pas directement dans l'espace de coordonnées de votre UI.

  • En revanche, pour un pavé tactile de type ordinateur portable, il existe une traduction pour contrôler un curseur.

  • Cette traduction implique un certain nombre de conversions complexes entre les plates-formes, comme l'accélération du curseur et le rejet de la paume.

  • Du côté de Compose, vous obtenez un pointeur correspondant à l'emplacement du curseur, qui agit généralement comme une saisie à la souris.

  • La plate-forme Android nous fournit des informations sur le pointage, les clics (y compris les clics secondaires ou tertiaires), et toutes ces informations sont encapsulées dans des MotionEvents.

  • Pour comprendre comment nous avons amélioré le comportement dans les dernières versions de Compose, examinons d'abord de plus près comment Compose gère ces événements de pointeur.

Les six phases de la gestion des entrées du pointeur

  • Je vais diviser ce processus en six phases différentes, que nous allons examiner une par une.

  • La première phase est le déplacement des MotionEvents vers Compose.

  • En réalité, Compose n'est qu'une vue : une vue très complexe qui fait beaucoup de choses, mais qui n'est qu'une vue. Cela signifie qu'elle doit fonctionner dans le système Android global pour l'entrée du pointeur.

  • Dans Compose, cela signifie que Compose, qui se trouve dans une fenêtre normale d'une activité normale, reçoit des MotionEvents : les MotionEvents de la plate-forme Android.

  • Compose doit ensuite les gérer et effectuer toutes les opérations que vous souhaitez effectuer dans Compose concernant la saisie par pointeur.

  • La première phase consiste à traduire ces MotionEvents en une représentation interne, indépendante de la plate-forme, dans Compose, que nous appelons PointerInputEvent.

  • Il s'agit d'un type interne, vous ne le verrez donc pas directement.

  • Si vous souhaitez examiner en détail ce qui se passe ici, vous pouvez consulter MotionEventAdapter, mais nous allons ignorer ces détails ici.

  • Nous disposons désormais d'une représentation indépendante de la plate-forme d'un MotionEvent dans Compose.

  • Ensuite, il s'avère que l'entrée se fait généralement au fil du temps.

  • Vous recevez une série d'événements d'entrée les uns après les autres. Il est donc très utile de savoir ce qui se passe au fil du temps.

  • Les gestes, le suivi de la vitesse, la détection des mouvements involontaires, etc. nécessitent de comprendre ce qui se passe à chaque instant.

  • L'étape suivante de ce pipeline de traitement consiste donc à ajouter ces informations temporelles, et un prétraitement sera effectué en fonction des événements de pointeur successifs.

  • Dans le code, cela revient à prendre le PointerInputEvent actuel, à le combiner avec ce qui s'est passé avec le précédent, puis à créer un InternalPointerEvent qui tient compte du temps.

  • Comme son nom l'indique, il s'agit d'un autre type interne, mais vous pouvez le considérer comme un mappage de différents ID de pointeur vers des modifications d'entrée de pointeur. PointerInputChanges est un type public.

  • C'est notre premier.

  • Vous remarquerez peut-être que, maintenant que nous sommes conscients du suivi du temps, nous pouvons également suivre l'apparition et la disparition des ID de pointeur.

  • Il n'y en a pas qu'un.

  • Si vous avez plusieurs doigts sur un écran tactile, vous pouvez avoir plusieurs pointeurs en même temps qui vont et viennent, et nous devons gérer cela.

  • Encore une fois, si vous souhaitez en savoir plus, vous pouvez consulter PointerInputChangeEventProducer, qui effectue toutes ces opérations, mais nous allons passer cette étape.

  • Nous avons encore beaucoup de choses à voir.

  • Nous disposons désormais d'une représentation Compose de l'entrée, qui contient des informations temporelles.

  • L'étape suivante consiste à déterminer ce qu'il faut faire de cet événement.

  • Où l'envoyer ?

  • Cela nous amène aux tests de chemin d'accès.

  • Imaginons une situation dans laquelle un MotionEvent est envoyé et où cette ComposeView remplit toute la fenêtre, voire tout l'écran.

  • Nous devons déterminer où l'envoyer.

  • Et comme Compose n'est qu'une vue, mais que nous avons des tonnes d'éléments à l'intérieur, un tas de nœuds, de boutons et d'éléments différents avec lesquels vous interagissez, nous devons faire tout le travail dans Compose pour déterminer où envoyer un événement qui se produit à une position spécifique et déterminer quel nœud est réellement intéressé par cet événement.

  • Ces nœuds de mise en page ont des coordonnées, des limites et des formes. Nous devons donc déterminer lequel est touché, le cas échéant.

  • Mais ce n'est pas non plus la vue la plus générale.

  • Il s'avère que nous aimons placer des éléments interactifs dans d'autres éléments interactifs, par exemple un bouton dans une liste.

  • Il s'avère que nous le faisons souvent.

  • Le cas général complet est donc qu'un événement d'entrée spécifique crée un chemin d'appel complet qui passe par chaque nœud interactif touché par les coordonnées de l'événement d'entrée.

  • Dans ce cas, il y a trois niveaux dans la hiérarchie d'imbrication.

  • Nous avons donc un chemin d'accès aux hits, mais cela ne suffit pas.

  • Vous vous souvenez que j'ai dit que nous avions plusieurs événements d'entrée de pointeur, plusieurs pointeurs qui pouvaient être enfoncés ?

  • Nous avons en fait besoin d'une arborescence complète des chemins d'accès aux hits.

  • Il n'y a pas qu'un seul chemin d'accès lorsque vous avez plusieurs pointeurs.

  • La phase suivante consiste donc à créer et à mettre à jour cette arborescence des chemins d'accès aux hits.

  • Voici un aperçu plus détaillé de ce qui se passe.

  • À gauche, nous avons des nœuds disposés visuellement, et en bas à gauche, vous pouvez voir la représentation hiérarchique, quels nœuds se trouvent à l'intérieur d'autres nœuds, lesquels sont des ancêtres ou des descendants les uns des autres.

  • Lorsqu'un pointeur est enfoncé, nous suivons ce chemin d'accès, le chemin d'accès complet dans l'arborescence, puis nous obtenons un chemin d'accès pour ce pointeur particulier.

  • Ce chemin d'accès devient ensuite une arborescence lorsque nous obtenons le deuxième pointeur.

  • Il peut croiser certains des mêmes nœuds que l'autre, mais il peut également interagir avec d'autres.

  • À droite, vous pouvez voir l'arborescence des chemins d'accès aux hits que nous gérons désormais.

  • De plus, les pointeurs peuvent apparaître et disparaître. Nous devons donc maintenir cette liste nous-mêmes au fil du temps pour nous assurer d'obtenir la liste complète des nœuds intéressés par chaque événement en cours à ce moment précis.

  • Très bien.

  • Nous disposons désormais d'une arborescence de chemins d'accès aux hits que nous gérons pour ces événements d'entrée, ce qui signifie que nous passons à la phase suivante.

  • Nous savons maintenant où envoyer ces événements d'entrée du pointeur, et quels nœuds sont intéressés par chaque événement spécifique.

  • Maintenant que nous le savons, nous pouvons effectuer une transformation de coordonnées.

  • Il s'avère qu'il est très pratique que votre nœud reçoive des événements d'entrée dans son propre espace de coordonnées local, ce qui vous évite d'avoir à effectuer vous-même un tas de calculs de coordonnées.

  • Une fois que nous le savons, nous pouvons prendre l'événement d'entrée du pointeur, le combiner avec les coordonnées du nœud concerné, puis créer l'événement PointerEvent public qui sera envoyé à tous et au modificateur d'entrée du pointeur.

  • Et c'est la dernière chose de notre phase 6, l'envoi de l'événement.

  • Il s'agit d'un événement d'entrée de pointeur qui s'est produit et qui est envoyé au nœud pour qu'il en fasse ce qu'il veut.

  • Nous procédons en trois étapes.

  • Nous avons une passe initiale, une passe principale et une passe finale.

  • La raison pour laquelle il existe trois passes différentes est qu'elles permettent aux nœuds de communiquer entre eux et de signaler leur intérêt pour des parties spécifiques d'événements d'entrée de pointeur spécifiques, puis d'en consommer différentes parties dans le cadre de la distribution.

  • La première passe, la passe initiale, va de l'ancêtre au descendant.

  • Cela peut être utile, par exemple, si un nœud de mise en page externe est un conteneur de défilement, qu'il sait qu'il a commencé à défiler et qu'il souhaite empêcher tous les événements de pointeur d'atteindre l'un de ses enfants.

  • Cela lui permet de consommer l'événement lors de la passe initiale, et les enfants ne recevront plus d'événements.

  • La deuxième passe est la passe principale.

  • Et c'est la seule qui va du descendant à l'ancêtre.

  • Comme son nom l'indique, il s'agit de la passe principale pour effectuer des opérations.

  • Par exemple, c'est là qu'un descendant consomme un événement de pression, en commençant à gérer un clic, puis qu'il a la possibilité de le faire avant l'un de ses parents si vous avez des éléments cliquables imbriqués.

  • Enfin, la dernière passe, la passe finale, est identique à la passe initiale, où elle va de l'ancêtre au descendant.

  • C'est là qu'un enfant peut indiquer à un parent qu'il a consommé une partie d'un événement.

  • Ainsi, si un enfant commence à gérer un clic, mais qu'un parent commence à faire défiler l'écran, l'enfant peut voir que le parent a effectué cette action, puis annuler le clic qu'il avait commencé à gérer.

  • Voilà pour les trois passes et les six phases.

  • Il s'agit de la base de tout ce qui se trouve au-dessus du système de saisie par pointeur.

  • La gestion des gestes, les éléments défilants, déplaçables et cliquables, ainsi que toutes les entrées de pointeur personnalisées sont tous basés sur cette fondation.

Mises à jour du pavé tactile et gestes à plusieurs doigts

  • Maintenant que nous avons vu tout cela, nous pouvons parler un peu plus en détail de la façon dont nous avons amélioré la prise en charge du comportement du pavé tactile dans Compose.

  • Ainsi, avant Compose 1.12, les événements d'entrée du pavé tactile étaient signalés comme un type de pointeur tactile, car il s'agit d'un doigt interagissant physiquement avec un pavé tactile pour contrôler le curseur.

  • Cependant, les utilisateurs s'attendent à ce que ce type d'entrée se comporte davantage comme une souris physique.

  • Nous avons donc remplacé le type de pointeur PointerType.Touch par PointerType.Mouse.

  • Grâce à ce changement, les utilisateurs bénéficieront d'une expérience beaucoup plus cohérente entre la souris et le pavé tactile pour tous les composants qui examinent ces informations.

  • Par exemple, le comportement des info-bulles : vous souhaitez probablement que le comportement soit le même pour un pavé tactile et une souris.

  • Le deuxième changement majeur que nous avons apporté au pavé tactile concerne les gestes à plusieurs doigts que vous pouvez effectuer sur un pavé tactile.

  • Auparavant, Compose utilisait le concept astucieux, mais un peu étrange, de faux doigts de la plate-forme Android.

  • Voyons ce que cela signifie.

  • Lorsque vous utilisez deux doigts sur un pavé tactile pour faire un panoramique, Android génère un seul faux doigt sur l'écran tactile qui se déplace là où se trouve le curseur.

  • Ensuite, lorsque vous déplacez vos doigts sur le pavé tactile, le faux doigt sur l'écran tactile se déplace de la distance et dans la direction appropriées pour imiter le comportement souhaité, puis il se relâche.

  • Ainsi, deux doigts se déplaçant vers le haut sur le pavé tactile sont traduits par un seul faux doigt sur l'écran tactile, qui se déplace également vers le haut, ce qui entraîne un défilement vers le bas.

  • Si, comme moi, vous utilisez la fonctionnalité de défilement inversé du trackpad, vous voudrez que le défilement se fasse dans l'autre sens.

  • Pour ce faire, il faut inverser la direction du faux doigt.

  • Désormais, deux doigts sur le pavé tactile qui se déplacent vers le haut sont traduits par un seul faux doigt sur l'écran tactile qui se déplace vers le bas, ce qui entraîne un défilement vers le haut, soit l'inverse de ce que nous avions auparavant.

  • Le scaling fonctionne de manière similaire.

  • Lorsque vous utilisez deux doigts sur un pavé tactile pour redimensionner, Android génère deux faux doigts sur l'écran tactile qui se déplacent vers le bas, puis se rapprochent ou s'éloignent, selon le geste, pour imiter le comportement souhaité de zoom avant ou arrière.

  • Depuis Compose 1.12 pour le panoramique et Compose 1.13 pour la mise à l'échelle, nous interceptons ces faux doigts.

  • Nous ne les envoyons pas tels que la plate-forme nous les fournit, mais nous les réinterprétons en de nouveaux types d'événements de pointeur : PanStart, PanMove et PanEnd pour le panoramique, et ScaleStart, ScaleChange et ScaleEnd pour le scaling.

  • Nous le faisons lors de la phase de conversion des événements Android en Compose dont j'ai parlé plus tôt.

  • Nous convertissons donc les événements Fake Finger en types appropriés, puis nous envoyons ces gestes définis par la plate-forme au Hit Path correspondant au curseur.

  • Voyons pourquoi cela est important.

  • Pourquoi faisons-nous cela ?

  • Imaginons que cette boîte soit une belle image que nous voulons pouvoir agrandir.

  • Lorsque vous utilisiez deux doigts sur un pavé tactile pour effectuer un zoom, il était possible que l'un des deux faux doigts ne touche pas l'élément.

  • Son chemin d'accès n'intersecterait pas l'image et commencerait en dehors des limites.

  • Ainsi, au lieu de redimensionner l'image, l'utilisateur pourrait la faire défiler.

  • S'il y avait un autre élément à faire défiler en dehors de l'image, il pourrait également commencer à défiler en même temps.

  • Ce n'est pas ce que l'utilisateur voulait.

  • Elle souhaitait évoluer.

  • Désormais, au lieu d'envoyer ces faux doigts, nous envoyons le geste de mise à l'échelle uniquement à l'élément situé sous la position du curseur, ce qui permet d'obtenir le comportement correct.

  • Peu importe où se trouve le curseur ou à quelle distance il se trouve du bord de l'élément que vous souhaitez redimensionner, l'élément sera désormais redimensionné correctement.

  • La bonne nouvelle, c'est que ces événements d'entrée réinterprétés sont utilisés par défaut dans les composants tels que "scrollable" et "transformable" pour offrir un comportement de haute fidélité aux entrées du pavé tactile si vous utilisez ces composants de base.

  • Et si vous utilisez un élément basé sur ces composants, vous bénéficierez de ce comportement amélioré en mettant à jour votre version de Compose vers la dernière version.

  • Si vous écrivez des modificateurs d'entrée de pointeur personnalisés, je vous encourage à examiner ces nouvelles API et à vous assurer que vous les prenez en charge pour offrir à vos utilisateurs la meilleure expérience possible sur tous ces différents périphériques d'entrée.

  • Et bien sûr, nous avons aussi ajouté des API de test pour que vous puissiez vérifier que votre appli gère correctement tous ces différents types d'entrées.

  • Ainsi, l'performTrackpadInput ressemble beaucoup aux API de test existantes pour la souris et l'écran tactile.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • En utilisant tous ces éléments, vous pouvez vérifier que votre application fonctionne correctement, quel que soit le périphérique d'entrée utilisé par l'utilisateur sur votre appareil.

  • J'espère que cela vous donne une meilleure idée de la façon dont un événement de pointeur passe d'un doigt sur un pavé tactile à votre application.

  • Je vais maintenant laisser la parole à Jeremy, qui va vous parler d'une utilisation très différente des pavés tactiles.

Présentation de la saisie indirecte

  • Merci.

  • Très bien.

  • Bonjour, je m'appelle Jeremy Walker.

  • Je travaille beaucoup sur la saisie, et j'ai pensé qu'il serait intéressant de vous parler de certaines des nouveautés que nous apportons à la saisie Compose et de leur impact sur vous.

  • Et plus particulièrement avec ce type de saisie indirecte, qui est apparu grâce à une nouvelle saisie basée sur un nouveau facteur de forme : les lunettes connectées.

  • Lorsque vous interagissez avec des lunettes, vous pouvez le faire de plusieurs façons : en regardant quelque chose, en utilisant votre voix ou en faisant des gestes. Mais je vais parler spécifiquement du pavé tactile.

  • Elles sont un peu différentes de celles dont Alex a parlé, car elles sont situées sur le côté de la tête, elles sont verticales, très larges et extrêmement courtes.

  • Lorsque nous avons réfléchi à la façon dont cela fonctionnerait dans Compose, nous avons imaginé que vous regardiez un petit bouton devant vous. Si vous déplacez un curseur pour essayer de le toucher, l'expérience ne sera pas très agréable, n'est-ce pas ?

  • Alors, comment allons-nous le prendre en charge ?

  • Pour rappel, voici à quoi ressemble l'UI de Glasses.

  • Il est spécialement conçu pour superposer du contenu de manière fluide sur votre champ de vision.

  • Mais c'est très simple, nous essayons de faire en sorte que ce soit simple.

  • Si nous examinons un exemple, vous pouvez voir qu'il s'agit d'une liste.

  • Elle est centrée sur un élément particulier.

  • Que voulons-nous vraiment faire ?

  • Nous voulons le sélectionner, ou peut-être passer au précédent ou au suivant.

  • Nous n'avons pas nécessairement besoin d'un emplacement à l'écran.

  • Celui-ci est un peu plus compliqué, mais vous pouvez voir qu'il y a trois options : vous pouvez raccrocher, réactiver le son ou désactiver la vidéo.

  • Mais en réalité, vous devez le sélectionner ou passer à un autre et le sélectionner.

  • Nous pensons donc qu'il n'est pas vraiment nécessaire de mapper directement un emplacement sur l'écran, ou dans ce cas, vos yeux.

  • Voilà à quoi cela ressemble.

  • Par exemple, vous pouvez voir ici que je passe d'un élément à un autre dans l'interface utilisateur des lunettes, et cela correspond à ce que nous voulons.

  • Vous pouvez appuyer dessus.

  • Nous avons donc examiné Android pour voir ce qui existait et que nous pouvions utiliser pour cela.

  • Et si vous avez déjà… Oh, désolé.

  • Nous allons passer rapidement sur ce point.

  • Laissez-moi revenir en arrière.

  • Bien.

  • Très bien.

  • Si vous avez déjà utilisé l'un de ces appareils et que vous l'avez associé à un appareil Android, vous savez que vous pouvez facilement passer d'un élément d'interface utilisateur à un autre.

  • Par exemple, avec la télécommande, vous pouvez voir que vous pouvez, si vous l'imaginez ou si vous l'avez associée à Google TV, et que vous avez une grille entière de films, vous pouvez appuyer sur les boutons fléchés pour accéder à celui que vous voulez, et vous pouvez appuyer sur le bouton pour le sélectionner, le bouton OK.

  • Si vous avez un menu de jeu et que vous utilisez cette manette, vous pouvez utiliser le pavé directionnel ou le joystick pour accéder à un élément d'interface utilisateur, par exemple dans un menu, et le sélectionner.

  • Idem pour le clavier.

  • Cela correspond donc à ce que nous voulons dans Compose.

  • Voici à nouveau les éléments, pour que vous puissiez vous faire une idée de ce à quoi cela ressemble.

  • Et si nous remontons un peu plus loin, il y avait ce type de pavé tactile associé à un téléviseur, qui utilisait une fonctionnalité appelée "navigation tactile".

  • L'idée est que les coordonnées X et Y ne sont pas du tout liées à l'écran.

  • Ils ne sont utilisés que pour la détection des gestes.

  • Si vous balayez l'écran vers la droite, c'est comme si vous appuyiez sur la flèche pour passer à l'élément suivant.

  • Si vous balayez l'écran vers la gauche, vous vous déplacez vers la gauche, le haut et le bas.

  • Nous obtenons ainsi à peu près ce que nous voulons.

  • Vous n'utilisez les coordonnées x et y que pour suivre la vitesse, par exemple, afin de déterminer les glissements, les balayages, etc. Cela nous donne donc l'essentiel de ce dont nous avons besoin pour commencer à utiliser Compose.

  • Ce n'est pas exact, car il est en quelque sorte mappé à la télévision. Nous devons donc nous assurer de ne pas suivre la voie de la télévision, de ne pas gâcher les téléviseurs plus tard, et de les séparer, mais nous y reviendrons dans un instant.

  • Mais cela nous donne un point de départ.

  • pour pouvoir utiliser ce type de navigation tactile.

Conception d'API pour la saisie indirecte et les nouveaux facteurs de forme

  • Voilà.

  • À quoi va ressembler la conception de l'API ?

  • Dans Compose, l'entrée la plus générale ou la plus courante est le toucher, n'est-ce pas ?

  • Nous avons donc beaucoup de travail à faire à ce sujet.

  • Nous avons donc dû déterminer comment cela s'y rapportait.

  • Pour rappel, voici à quoi ressemble PointerEvent.

  • Vous pouvez voir ici que vous avez le type, qui est le type de mouvement vers le bas dans le haut, puis vous avez ce PointerInputChange, qui est simplement une liste. Vous pouvez le voir comme des doigts.

  • Si vous avez trois doigts levés, vous aurez trois éléments dans la liste.

  • Nous voulions donc que cela ressemble beaucoup à ceci.

  • Il ne s'agit pas d'un module d'interface utilisateur, qui est un module de niveau bien inférieur à celui que la plupart des développeurs utilisent lorsqu'ils gèrent les entrées.

  • Ils se situeront au niveau de la couche de base.

  • C'est là que se trouvent tous nos détecteurs de gestes.

  • En tant que développeurs, vous n'avez pas à vous soucier de toutes ces entrées détaillées.

  • Vous voulez simplement savoir si un glissement, un clic ou un balayage a eu lieu, puis vous effectuez votre action en fonction de cela.

  • Nous avons donc un ensemble de code qui s'en charge pour vous, et qui est alimenté par ces éléments en arrière-plan.

  • Nous avons donc commencé à réfléchir à la manière de faciliter au maximum la prise en charge de ces nouveaux types par le code existant dans Foundation.

  • Nous avons donc créé un nouveau type qui ressemble beaucoup au premier.

  • On l'appelle simplement "indirect".

  • Et indirectement, cela signifie qu'il n'y a pas de contact direct avec l'écran.

  • Il vous indique que cette fonctionnalité est en cours de développement et qu'il ne faut pas l'envisager du point de vue de l'interaction tactile.

  • N'oubliez pas que nous ne faisons que gérer les gestes ici et naviguer entre les éléments d'interface utilisateur.

  • Le type est le même.

  • Il contient une liste de doigts.

  • Dans ce cas, il s'agit de IndirectPointerInputChange.

  • Nous avons donc fait cela, et ça a très bien fonctionné.

  • Mais nous avons également découvert un autre élément, appelé "axe de mouvement directionnel principal".

  • C'est un peu long, mais vous comprendrez mieux quand je vous expliquerai, car la façon dont cela s'est produit est assez intéressante.

  • Comment est-il né ?

  • Encore une fois, vous avez ce pavé tactile sur le côté de votre tête, qui est large et court.

  • À ce stade, nous avions l'idée de la navigation tactile.

  • Nous pouvons donc balayer l'écran vers la gauche ou vers la droite.

  • Et ça fonctionne bien.

  • Mais si vous pensez à la façon dont les trackpads existent aujourd'hui, n'est-ce pas ?

  • Lorsque vous allez vers la gauche ou la droite, vous allez vers la gauche ou la droite sur le pavé tactile avec le contenu de votre UI.

  • Si vous balayez l'écran vers le haut ou vers le bas, vous faites défiler la liste vers le haut ou vers le bas.

  • Si vous essayez de faire cela avec des lunettes, ce n'est pas trop mal, n'est-ce pas ?

  • Mais si vous faites cela, l'expérience n'est pas optimale.

  • Nous avons découvert que le mouvement le plus naturel était d'appuyer, mais aussi de balayer le côté des lunettes.

  • Nous avons donc mené plusieurs études auprès des utilisateurs, et il s'avère qu'ils s'attendent à ce que le balayage horizontal déplace le contenu verticalement, et non horizontalement.

  • Vous pouvez vous en rendre compte si vous essayez de l'imaginer. Par exemple, lorsque vous balayez l'écran, si vous avez une liste devant vous, vous pouvez la parcourir vers l'avant ou vers l'arrière.

  • Nous prenons donc une entrée bidimensionnelle et nous essayons de la considérer comme une entrée unidimensionnelle.

  • C'est de là qu'est venue l'idée de l'axe de mouvement principal : vous pouvez vous déplacer horizontalement, et l'élément se déplacera verticalement.

  • Vous pouvez donc spécifier sur l'appareil lui-même.

  • Vous pouvez dire : "Hé, ce n'est pas un pavé tactile normal."

  • Je veux que la direction principale soit horizontale, et c'est en quelque sorte le seul axe que je veux suivre.

  • C'est de là que ça vient.

  • OK.

  • Nous sommes donc plutôt satisfaits.

  • Compose gère tout.

  • Et vous obtenez cette belle implémentation verticale.

  • Vous pouvez y réfléchir.

  • Nous avons donc mappé les commandes de sorte que, avec la définition horizontale, vous n'ayez plus qu'un bouton "Avancer" et un bouton "Reculer".

  • Il n'y a pas de haut, de bas, de gauche ni de droite.

  • Nous n'avons que les boutons "Suivant" et "Précédent".

  • Ensuite, nous pouvons mapper cela si vous connaissez le focus avec "Suivant" et "Précédent".

  • Passons maintenant aux boutons "Suivant" et "Précédent".

  • Et cela fonctionne très bien pour les vidéos verticales.

  • Et il est encore plus agréable.

  • Il fonctionne avec les images horizontales, n'est-ce pas ?

  • car vous n'avez qu'un ensemble d'éléments sélectionnés.

  • Il vous suffit de passer au suivant ou au précédent, et cela fonctionne avec les deux.

  • C'est un cas où nous avons défini et résolu tout cela dans Compose, mais ce serait vraiment bien si l'appareil lui-même pouvait le dire, car il sait s'il a une forme bizarre et s'il ne veut que cet axe principal, mais il n'a aucun moyen de le définir.

  • Nous avons donc dû revenir à la plate-forme et dire : "OK, nous devons définir ce concept dans la plate-forme pour pouvoir le standardiser et pour que les appareils eux-mêmes puissent le spécifier, afin que Compose puisse le prendre entièrement en charge."

  • Nous avons donc travaillé dans Compose, nous avons avancé dans Compose, puis nous sommes revenus en arrière, et nous avons en quelque sorte emporté Compose avec nous.

  • C'est exactement ce que nous avons fait.

  • Nous avons créé cette nouvelle API pour que vous ou l'appareil puissiez spécifier cela.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • Si vous vous souvenez, plus tôt, lorsque j'ai parlé de la saisie tactile ou de la navigation tactile, j'ai dit qu'elle était principalement destinée aux téléviseurs.

  • Nous ne voulions pas interrompre ces implémentations.

  • Nous avons donc ajouté un autre champ pour spécifier ce nouveau type d'entrée de verre.

  • OK.

  • Qu'est-ce que cela signifie pour vous ?

  • Il s'agit d'une version plus spécifique de ce que vous obtenez avec un détecteur de gestes.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • Si vous utilisez Foundation, cela fonctionnera directement avec les détecteurs de gestes.

  • Nous l'avons fait pour vous, vous n'avez donc rien à faire.

  • Mais si vous voulez créer une application pour les lunettes, vous pouvez utiliser notre bibliothèque Glimmer. Nous avons un détecteur de gestes spécial qui vous donne uniquement, vous savez, pour que vous n'ayez pas à les combiner, vous pouvez utiliser uniquement celui-ci, obtenir le clic, l'image suivante et l'image précédente.

Tester la saisie indirecte au pointeur

  • OK.

  • Nous avons donc vu comment l'API fonctionne, sur la plate-forme et dans Compose.

  • Qu'en est-il des tests ?

  • Voici à quoi ressemble le test du capteur tactile.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • Il vous suffit de dire performTouchInput, puis de faire un geste.

  • Il existe de nombreux gestes différents, et nous créons tous les petits événements pour que le geste se produise.

  • Comment cela fonctionne-t-il globalement ?

  • Vous avez le module d'UI Compose et le module de test d'UI, qui communiquent en quelque sorte, du moins en termes d'entrées, à l'aide de MotionEvents.

  • De plus, le module de test de l'UI utilise un mécanisme de mise en file d'attente, qui est entièrement basé sur MotionEvent.

  • Pourquoi est-ce que je vous dis cela ?

  • En effet, il est impossible de créer de manière programmatique un MotionEvent semblable à un événement d'entrée indirect. C'était une bonne nouvelle pour moi, car cela signifiait que je devais réécrire toutes les communications entre les modules et le système de mise en file d'attente.

  • Mais je l'ai fait.

  • Qu'est-ce que cela signifie pour vous ?

  • Si vous avez l'habitude de tester la saisie tactile, voici ce que vous obtenez pour la saisie indirecte.

  • Il vous suffit d'ajouter "indirect" au nom.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput (
    IndirectPointerEventPrimaryDirectionalMotionAxis.X,
    inputDeviceSize,
    )
    { click(center) }
    }
  • Ici, je transmets des paramètres supplémentaires.

  • Vous reconnaissez probablement le premier, qui vous permet de spécifier l'axe horizontal.

  • Et la taille d'entrée serait évidemment la taille du pavé tactile.

  • Cela ne change pas grand-chose.

  • Si vous regardez le code source, vous pouvez voir que j'ai simplement écrit une fonction d'extension qui ressemble à ceci.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput
    { click(center) }
    
    }
  • OK.

  • Cela devrait vous donner une idée de tout le travail que nous avons accompli pour prendre en charge les pavés tactiles sur vos montures ou sur vos lunettes connectées.

  • Mais nous faisons ces exercices, je fais ces exercices pour le suivi des yeux et des mains également, donc il y a encore beaucoup à venir.

  • En résumé, que devez-vous retenir de ma partie ?

Résumé et points clés à retenir

  • Ainsi, que vous utilisiez un pavé tactile avec un téléphone, des lunettes ou tout autre appareil d'entrée, nous y réfléchissons et nous essayons d'anticiper.

  • Si vous utilisez ces détecteurs de gestes Foundation, cela fonctionne donc directement.

  • C'est le premier point.

  • Deuxièmement, Compose est le kit d'UI principal, et nous continuerons à explorer et à prendre en charge de nouveaux types d'entrées, à la fois dans la plate-forme et dans le kit d'UI, comme je l'ai fait ici.

  • Et bien sûr, pour tester les API, je souhaite conserver une surface et un paradigme familiers afin que vous puissiez migrer et tester ces nouveaux types aussi facilement que possible.

  • Merci, j'espère que cela vous a été utile.

Code

Extraits de code

Extrait de code Kotlin à 15:47 dans "Trackpad updates and multi-finger gestures"
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
Extrait de code Kotlin à 25:08 dans "Conception d'API pour les entrées indirectes et les nouveaux facteurs de forme"
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
Extrait de code Kotlin à 25:32 dans "API design for indirect input and new form factors"
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
Extrait de code Kotlin à 26:11 dans "Tester la saisie indirecte par pointeur"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }

}
Extrait de code Kotlin à 27:08 dans "Tester la saisie indirecte au pointeur"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
Extrait Kotlin à 27:23 dans "Tester la saisie indirecte par pointeur"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }

}