Apps-Grundlage

Eingabeinteraktionen über den Touchscreen hinaus mit Jetpack Compose skalieren

Lautsprecher

  • Alex Vanyo

    Softwareentwickler

  • Jeremy Walker

    Compose Software Engineer

Info

Über den Vortrag

Da neue Formfaktoren über den Touchscreen hinausgehen, wurde das Eingabesystem von Compose weiterentwickelt, um dieser Herausforderung gerecht zu werden. Die mehrschichtige Architektur bildet die Grundlage für die nahtlose Unterstützung von erweiterten Trackpad-Gesten bis hin zu neuartigen Hardwareeingaben.

Anhand von klaren Architekturaufschlüsselungen und praktischen Codebeispielen wird der Lebenszyklus eines Eingabeereignisses von der Betriebssystemplattformebene bis hin zu Compose-Gestendetektoren und UI-Komponenten nachvollzogen. Sie erfahren, wie Sie Ereignisse im gesamten Stack verfolgen, wichtige Architekturmuster für neue Eingabetypen anwenden und komplexe Gesteninteraktionen in Ihren eigenen Apps zuverlässig debuggen können.

Zusammenfassung

  • Jetpack Compose verarbeitet eingehende Android-Zeigerereignisse über eine sechsstufige Pipeline. Diese Pipeline verwaltet einen Trefferpfadbaum und sendet Ereignisse in drei Durchgängen über UI-Knoten.
  • Trackpad-Eingaben in Compose werden jetzt Mausereignissen statt Berührungen zugeordnet. Compose wandelt auch gefälschte Fingerereignisse der Plattform in dedizierte Schwenk- und Skalierungszeigerereignisse um.
  • Trackpads von Smart Glasses führen indirekte Eingabetypen ein, anstatt Bildschirmkoordinaten zu berühren. Sie verwenden eine primäre Richtungsbewegungsachse, um horizontales Wischen in vertikale Navigation zum nächsten oder vorherigen Element umzuwandeln.
  • Die Testunterstützung wurde über die Touch-Bedienung hinaus um neue Methoden wie performTrackpadInput erweitert. Diese APIs bieten vertraute Testoberflächen, um zu prüfen, ob Apps auf Geräten ohne Touchscreen korrekt funktionieren.

Gemini Notebook

Themen

  • jetpack-compose
  • pointer-input
  • Touchpads
  • gesten, bewegungen, touch-gesten
  • intelligent-eyewear
  • Bedienungshilfen

Transkript

Einführung und Eingabephilosophie

  • Hallo zusammen!

  • Wir hoffen, dass Sie eine gute Zeit beim Android Developer Summit haben, und danken Ihnen für die Teilnahme an unserem Vortrag.

  • Ich bin Alex Vanyo, Software Engineer im Compose-Team, und ich bin hier mit Jeremy, ebenfalls Software Engineer im Compose-Team.

  • Wir haben beide daran gearbeitet, das Eingabesystem von Jetpack Compose zu verbessern. Heute werden wir darüber sprechen, wie wir es über den Touchscreen hinaus skaliert haben, um die Unterstützung von Eingaben ohne Touchbedienung zu vereinfachen.

  • Eingaben umfassen im Allgemeinen alle Arten, wie Nutzer mit Ihrer App interagieren.

  • Touchscreens sind wahrscheinlich das Erste, woran Sie bei einer mobilen App denken, aber sie sind definitiv nicht die einzige Art der Eingabe.

  • Andere Eingabemethoden sind physische Tastaturen, Mäuse, Trackpads, Eingabestifte, Controller und Drehregler.

  • Außerdem gibt es neue Eingabetypen wie Trackpads auf Smart Glasses, Augen- und Handtracking in XR sowie Finger- und Handgelenksbewegungen auf Wear.

  • Und wenn Sie noch allgemeiner werden möchten, gibt es auch andere Arten von Eingaben: Dinge wie Software-IMEs für die Textbearbeitung, Mikrofone, Kameras und andere Gerätesensoren.

  • Sie müssen damit rechnen, dass Ihre Apps mit einer Vielzahl von Eingabetypen verwendet werden.

  • Ein Grund dafür ist die immer größere Vielfalt an Geräten, auf denen Anwendungen ausgeführt werden, und die zunehmende Anpassungsfähigkeit dieser Geräte.

  • Geräteformfaktoren haben möglicherweise eine am häufigsten verwendete Eingabemethode. Es ist jedoch wichtig, nicht davon auszugehen, dass ein bestimmter Geräteformfaktor die Verwendung eines bestimmten Eingabetyps impliziert.

  • Ein Chromebook hat zwar ein integriertes Trackpad und eine Tastatur, diese Eingabetypen sind jedoch nicht nur für diesen Formfaktor verfügbar.

  • Auch auf Smartphones, Tablets und Fernsehern werden physische Tastaturen, Trackpads und Mäuse verwendet. Einige Fernseher haben sogar Touchscreens.

  • Ein zweiter wichtiger Grund, mehrere Eingabetypen zu erwarten und zu unterstützen, ist die Barrierefreiheit.

  • Wenn Sie Nutzern die Möglichkeit geben, das Eingabegerät ihrer Wahl zu verwenden und es nach Belieben zu wechseln, machen Sie Anwendungen barrierefreier und nutzerfreundlicher.

  • Je nach Gerichtsbarkeit kann es gesetzlich vorgeschrieben sein, dass Ihre Anwendung für diese Eingabetypen ein bestimmtes, kompatibles Verhalten aufweist, insbesondere bei Tastaturen und Mäusen.

  • Kurz gesagt: Du solltest Unterstützung für verschiedene Eingabetypen unabhängig vom Formfaktor einbauen.

  • Sehen wir uns also an, wie Jetpack Compose dazu beiträgt, dass deine Apps diese Nutzeranforderungen erfüllen.

  • Die Philosophie von Compose ist im Allgemeinen, dass sich diese verschiedenen Eingabetypen mit Standardkomponenten sofort angemessen verhalten sollten, ohne dass Sie etwas Besonderes tun müssen.

  • Und dann sollten Sie die Tools haben, die Sie benötigen, um das Verhalten nach Ihren Wünschen anzupassen.

  • Über diese ersten Eingabetypen hinaus werden wir, sobald weitere Eingabetypen von der zugrunde liegenden Plattform unterstützt werden, die Unterstützung für diese in Compose-Komponenten einbauen. So können Sie das Verhalten Ihrer Anwendungen verbessern, indem Sie einfach Ihre Compose-Version aktualisieren.

  • In diesem Vortrag sehen wir uns zwei Beispiele an, die beide erst vor Kurzem zu Compose hinzugefügt wurden.

  • Beide sind Trackpad-ähnliche Geräte, werden aber auf sehr unterschiedliche Weise verwendet.

  • Beginnen wir mit einigen aktuellen Arbeiten an Trackpads im Laptop-Stil.

  • Ein Trackpad ist ein zeigerbasiertes Eingabegerät.

  • Genauer gesagt erhalten Sie bei der Verwendung eines Touchpads zweidimensionale Koordinaten.

  • Im Gegensatz zu einem Touchscreen befinden sich die Koordinaten der Finger auf dem Trackpad jedoch nicht direkt im Koordinatenbereich der Benutzeroberfläche.

  • Stattdessen wird die Bewegung auf dem Trackpad in die Bewegung eines Cursors übersetzt, wie bei einem Laptop.

  • Dabei werden viele komplizierte Plattformkonvertierungen vorgenommen, z. B. Cursorbeschleunigung und Handballenerkennung.

  • In Compose erhalten Sie dann einen Zeiger, der der Cursorposition entspricht und normalerweise wie eine Mauseingabe funktioniert.

  • Von der Android-Plattform erhalten wir Informationen zu Hover-Aktionen, Klicks, einschließlich sekundärer oder tertiärer Klicks, und all diese Informationen werden in MotionEvents zusammengefasst.

  • Um zu verstehen, wie wir das Verhalten in den aktuellen Versionen von Compose verbessert haben, sehen wir uns zuerst einen allgemeinen Überblick darüber an, wie Compose diese Zeigerereignisse genauer verarbeitet.

Die sechs Phasen der Verarbeitung von Zeigereingaben

  • Ich werde das in sechs verschiedene Phasen unterteilen, die wir nacheinander durchgehen.

  • In der ersten Phase werden MotionEvents in Compose verschoben.

  • Compose ist im Grunde nur eine Ansicht – eine sehr komplizierte Ansicht, die viel leistet, aber eben nur eine Ansicht. Das bedeutet, dass sie im Android-System für Zeigereingaben funktionieren muss.

  • In Compose bedeutet das, dass Compose, da es sich in einem normalen Fenster innerhalb einer normalen Aktivität befindet, MotionEvents erhält: die MotionEvents der Android-Plattform.

  • Und dann muss Compose sie verarbeiten und alles tun, was Sie in Compose im Zusammenhang mit Zeigereingaben tun möchten.

  • In der ersten Phase werden diese MotionEvents in eine interne, plattformunabhängige Darstellung in Compose übersetzt, die wir als PointerInputEvent bezeichnen.

  • Das ist ein interner Typ, den Sie nicht direkt sehen.

  • Wenn Sie sich alle Details dazu ansehen möchten, können Sie sich den MotionEventAdapter ansehen. Wir überspringen diese Details hier jedoch.

  • Wir haben jetzt also eine plattformunabhängige Darstellung eines MotionEvent in Compose.

  • Als Nächstes stellt sich heraus, dass die Eingabe normalerweise im Laufe der Zeit erfolgt.

  • Sie erhalten eine Reihe von Eingabeereignissen nacheinander. Es ist also sehr nützlich, zu wissen, was im Laufe der Zeit passiert.

  • Dinge wie Gesten, Geschwindigkeits-Tracking und Slop-Erkennung müssen alle zu jedem Zeitpunkt wissen, was gerade passiert.

  • Der nächste Schritt in dieser Verarbeitungspipeline besteht also darin, diese Zeitinformationen hinzuzufügen. Außerdem werden einige Vorverarbeitungsschritte basierend auf aufeinanderfolgenden Zeigerereignissen ausgeführt.

  • Im Code können Sie sich das so vorstellen, dass das aktuelle PointerInputEvent mit dem vorherigen kombiniert wird, um ein zeitbewusstes InternalPointerEvent zu erstellen.

  • Dies ist ein weiterer interner Typ, wie der Name schon sagt. Sie können sich diesen Typ als eine Zuordnung verschiedener Zeiger-IDs zu Zeigereingabeänderungen vorstellen. „PointerInputChanges“ ist ein öffentlicher Typ.

  • Das ist unser erster.

  • Wenn wir über verschiedene Zeiger-IDs nachdenken, fällt uns vielleicht auf, dass wir jetzt, da wir die Zeit im Blick haben, auch verfolgen können, wann Zeiger-IDs kommen und gehen.

  • Es gibt nicht nur einen.

  • Wenn Sie mehrere Finger auf einem Touchscreen haben, können mehrere Zeiger gleichzeitig erscheinen und verschwinden. Das müssen wir berücksichtigen.

  • Wenn Sie sich die Details ansehen möchten, können Sie sich den PointerInputChangeEventProducer ansehen, der all dies tut. Wir überspringen das aber.

  • Wir haben noch viel vor uns.

  • Wir haben jetzt eine Compose-Darstellung der Eingabe, die Zeitinformationen enthält.

  • Im nächsten Schritt müssen Sie entscheiden, was mit diesem Ereignis geschehen soll.

  • Wohin sollen wir es senden?

  • Das führt uns zum Hit-Path-Testen.

  • Stellen wir uns eine Situation vor, in der ein MotionEvent gesendet wird und diese ComposeView das gesamte Fenster, vielleicht den gesamten Bildschirm, ausfüllt.

  • Wir müssen herausfinden, wohin wir es senden sollen.

  • Und da Compose nur eine Ansicht ist, aber viele Dinge enthält, z. B. verschiedene Knoten, Schaltflächen und Elemente, mit denen Sie interagieren, müssen wir in Compose die gesamte Arbeit erledigen, um herauszufinden, wohin ein Ereignis gesendet werden soll, das an einer bestimmten Position auftritt, und welcher Knoten tatsächlich daran interessiert ist.

  • Diese Layoutknoten haben Koordinaten, Grenzen und Formen. Wir müssen also herausfinden, welcher davon, wenn überhaupt, getroffen wird.

  • Aber das ist auch nicht die allgemeinste Ansicht.

  • Es stellt sich heraus, dass wir gerne interaktive Elemente in andere interaktive Elemente einfügen, z. B. eine Schaltfläche in eine Liste.

  • Das machen wir offenbar ziemlich oft.

  • Der allgemeine Fall ist also, dass ein bestimmtes Eingabeereignis einen vollständigen Trefferpfad erstellt, der durch jeden interaktiven Knoten verläuft, der von den Koordinaten des Eingabeereignisses getroffen wird.

  • In diesem Fall gibt es also die drei, die die Verschachtelungshierarchie durchlaufen.

  • Wir haben also einen Trefferpfad, aber das reicht noch nicht.

  • Erinnern Sie sich, dass ich gesagt habe, dass wir mehrere Zeigereingabeereignisse haben, mehrere Zeiger, die möglicherweise gedrückt sind?

  • Wir benötigen einen vollständigen Hit-Pfad-Baum.

  • Wenn Sie mehrere Zeiger haben, gibt es nicht nur einen Trefferpfad.

  • Das ist also die nächste Phase: das Erstellen und Aktualisieren dieses Hit-Pfad-Baums.

  • Hier sehen Sie genauer, was passiert.

  • Links sehen Sie die Knoten, die visuell angeordnet sind, und unten links die hierarchische Darstellung, welche Knoten sich in anderen Knoten befinden und welche Vorgänger oder Nachfolger voneinander sind.

  • Wenn ein Zeiger nach unten bewegt wird, verfolgen wir diesen Trefferpfad, den vollständigen Pfad durch den Baum, und erhalten dann einen Trefferpfad für diesen bestimmten Zeiger.

  • Dieser Trefferpfad wird dann zu einem Baum, wenn wir den zweiten Zeiger erhalten.

  • Es kann sich mit einigen der gleichen Knoten wie das andere überschneiden, aber auch mit anderen interagieren.

  • Rechts sehen Sie den Hit-Pfad-Baum, den wir jetzt pflegen.

  • Und Zeiger können kommen und gehen, also müssen wir das im Laufe der Zeit selbst verwalten, um sicherzustellen, dass wir die vollständige Liste der Knoten erhalten, die an jedem laufenden Ereignis zu diesem Zeitpunkt interessiert sind.

  • Okay.

  • Wir haben jetzt einen Trefferpfadbaum, den wir für diese Eingabeereignisse verwalten. Das bedeutet, dass wir mit der nächsten Phase fortfahren können.

  • Jetzt wissen wir, wohin wir diese Zeigereingabeereignisse senden, also welche Knoten an den einzelnen Ereignissen interessiert sind.

  • Da wir das jetzt wissen, können wir eine Koordinatentransformation durchführen.

  • Es ist sehr praktisch, wenn der Knoten Eingabeereignisse innerhalb seines eigenen lokalen Koordinatenraums erhält, sodass Sie nicht selbst eine Menge Koordinatenberechnungen durchführen müssen.

  • Sobald wir das wissen, können wir das Zeigereingabeereignis mit den Koordinaten des relevanten Knotens kombinieren und so das öffentliche PointerEvent erstellen, das an alle und den Zeigereingabemodifikator gesendet wird.

  • Und das ist der letzte Schritt in Phase 6: das Senden des Ereignisses.

  • Dies ist ein Zeigereingabeereignis, das stattgefunden hat und an den Knoten gesendet wird, damit dieser damit machen kann, was er möchte.

  • Wir tun dies in drei verschiedenen Durchgängen.

  • Es gibt einen ersten Durchlauf, einen Hauptdurchlauf und einen letzten Durchlauf.

  • Der Grund für die drei verschiedenen Durchgänge ist, dass Knoten so miteinander kommunizieren und Interesse an bestimmten Teilen bestimmter Zeigereingabeereignisse bekunden können. Anschließend können sie verschiedene Teile davon im Rahmen der Weiterleitung nutzen.

  • Der erste Durchlauf erfolgt also von der übergeordneten zur untergeordneten Ansicht.

  • Das ist beispielsweise nützlich, wenn ein äußerer Layoutknoten ein scrollbarer Container ist, der weiß, dass er mit dem Scrollen begonnen hat, und verhindern möchte, dass alle Zeigerereignisse seine untergeordneten Elemente erreichen.

  • Dadurch kann es im ersten Durchlauf konsumiert werden und die untergeordneten Elemente erhalten keine weiteren Ereignisse.

  • Der zweite Durchlauf ist der Hauptdurchlauf.

  • Und dies ist die einzige, die von einem Nachkommen zu einem Vorfahren führt.

  • Wie der Name schon sagt, ist dies der primäre Durchlauf, um Dinge zu erledigen.

  • Hier wird beispielsweise ein Down-Ereignis von einem untergeordneten Element verarbeitet, um einen Klick zu verarbeiten. Wenn Sie verschachtelte anklickbare Elemente haben, kann das untergeordnete Element dies vor allen übergeordneten Elementen tun.

  • Der letzte Durchlauf sieht dann genauso aus wie der erste, also von Vorfahre zu Nachkomme.

  • Hier kann ein untergeordnetes Element einem übergeordneten Element mitteilen, dass es einen Teil eines Ereignisses verarbeitet hat.

  • Wenn also ein untergeordnetes Element mit der Verarbeitung eines Klicks beginnt, aber dann ein übergeordnetes Element mit dem Scrollen beginnt, kann das untergeordnete Element sehen, dass das übergeordnete Element dies getan hat, und dann den Klick abbrechen, den es zu verarbeiten begonnen hatte.

  • Das sind also die drei Durchgänge und die sechs Phasen.

  • Diese sind die Grundlage für alles, was auf dem Zeigereingabesystem aufbaut.

  • Alle Gesten, scrollbaren, ziehbaren und anklickbaren Elemente sowie alle benutzerdefinierten Zeigereingaben basieren auf dieser Grundlage.

Touchpad-Updates und Touchgesten mit mehreren Fingern

  • Nachdem wir das alles besprochen haben, können wir uns nun die Details ansehen, wie wir die Unterstützung für das Trackpad-Verhalten in Compose verbessert haben.

  • Vor Compose 1.12 wurden Trackpad-Eingabeereignisse als Touch-Eingabe gemeldet, da ein Finger physisch mit einem Trackpad interagiert, um den Cursor zu steuern.

  • Nutzer erwarten jedoch, dass sich dieser Eingabetyp eher wie eine physische Maus verhält.

  • Wir haben also den Zeigertyp von PointerType.Touch in PointerType.Mouse geändert.

  • Durch diese Änderung wird die Maus- und Trackpad-Nutzung für alle Komponenten, die diese Informationen berücksichtigen, deutlich einheitlicher.

  • Ein Beispiel hierfür ist das Verhalten von Kurzinfos: Sie möchten wahrscheinlich dasselbe Verhalten für ein Trackpad und eine Maus für Kurzinfos haben.

  • Die zweite große Änderung am Trackpad betrifft die Touchgesten mit mehreren Fingern, die Sie auf einem Trackpad ausführen können.

  • Bisher hat Compose das clevere, aber etwas unheimliche Konzept der Android-Plattform von „Fake-Fingern“ verwendet.

  • Sehen wir uns an, was das bedeutet.

  • Wenn Sie mit zwei Fingern auf einem Trackpad schwenken, generiert Android einen einzelnen simulierten Touchscreen-Finger, der an der Position des Cursors nach unten bewegt wird.

  • Wenn Sie dann Ihre Finger auf dem Trackpad bewegen, bewegt sich der virtuelle Finger auf dem Touchscreen um die richtige Distanz und in die richtige Richtung, um das gewünschte Verhalten zu imitieren, und wird dann losgelassen.

  • Zwei Finger, die sich auf dem Trackpad nach oben bewegen, werden also in einen einzelnen virtuellen Finger auf dem Touchscreen übersetzt, der sich ebenfalls nach oben bewegt, was wiederum zu einem Scrollen nach unten führt.

  • Wenn Sie wie ich die Funktion „Umgekehrtes Scrollen“ für das Trackpad verwenden, möchten Sie wahrscheinlich, dass das Scrollen in die andere Richtung erfolgt.

  • Dazu wird die Richtung des künstlichen Fingers umgekehrt.

  • Wenn Sie jetzt zwei Finger auf dem Trackpad nach oben bewegen, wird dies in einen einzelnen simulierten Finger auf dem Touchscreen übersetzt, der sich nach unten bewegt. Dadurch wird nach oben gescrollt, also das Gegenteil von dem, was wir vorher hatten.

  • Die Skalierung funktioniert ähnlich.

  • Wenn Sie zwei Finger auf einem Trackpad zum Skalieren verwenden, generiert Android zwei virtuelle Touchscreen-Finger, die sich je nach Geste nach unten und dann näher zusammen oder weiter auseinander bewegen, um das gewünschte Verhalten beim Heran- oder Herauszoomen zu simulieren.

  • Seit Compose 1.12 für das Schwenken und Compose 1.13 für das Skalieren werden diese simulierten Finger jetzt abgefangen.

  • Wir senden sie nicht so, wie sie von der Plattform bereitgestellt werden, sondern interpretieren sie in neue Zeigerereignistypen um: PanStart, PanMove und PanEnd für das Schwenken sowie ScaleStart, ScaleChange und ScaleEnd für das Skalieren.

  • Das geschieht in der Phase der Konvertierung von Android- in Compose-Ereignisse, die ich vorhin erwähnt habe.

  • Wir konvertieren also Fake Finger-Ereignisse in die entsprechenden Typen und senden dann diese plattformdefinierten Gesten an den Hit Path, der dem Cursor entspricht.

  • Sehen wir uns an, warum das wichtig ist.

  • Warum machen wir das überhaupt?

  • Angenommen, dieses Feld hier ist ein schönes Bild, das wir vergrößern möchten.

  • Wenn Sie zwei Finger auf einem Trackpad zum Skalieren verwendet haben, konnte es passieren, dass einer der beiden simulierten Finger das Element verfehlt hat.

  • Der Trefferpfad würde sich nicht mit dem Bild überschneiden und außerhalb der Grenzen beginnen.

  • Anstatt zu skalieren, würde das Bild also vielleicht scrollen.

  • Wenn es außerhalb des Bildes ein weiteres scrollbares Element gäbe, würde dieses vielleicht auch mitscrollen.

  • Das ist nicht das, was der Nutzer wollte.

  • Sie wollten skalieren.

  • Anstatt also diese gefälschten Finger zu senden, senden wir jetzt die Skalierungsgeste nur an das Element unter der Cursorposition, was dann zum richtigen Verhalten führt.

  • Egal, wo sich der Cursor befindet oder wie nah er am Rand des Elements ist, das Sie skalieren möchten, die Skalierung funktioniert jetzt wie gewünscht.

  • Die gute Nachricht für Sie alle ist, dass diese neu interpretierten Eingabeereignisse standardmäßig in Komponenten wie „scrollable“ und „transformable“ verwendet werden, um ein Verhalten mit höherer Qualität für Trackpad-Eingaben zu ermöglichen, wenn Sie diese grundlegenden Bausteine verwenden.

  • Wenn Sie etwas verwenden, das auf diesen Komponenten aufbaut, erhalten Sie das verbesserte Verhalten, indem Sie Ihre Compose-Version auf die neueste Version aktualisieren.

  • Wenn Sie benutzerdefinierte Pointer-Eingabemodifikatoren schreiben, sollten Sie sich diese neuen APIs ansehen und sicherstellen, dass Sie sie unterstützen, um Ihren Nutzern die bestmögliche Erfahrung auf allen diesen verschiedenen Eingabegeräten zu bieten.

  • Und natürlich haben wir auch Test-APIs hinzugefügt, mit denen Sie prüfen können, ob Ihre App alle diese verschiedenen Eingabetypen korrekt verarbeitet.

  • Die performTrackpadInput ähnelt also den vorhandenen Test-APIs für Maus und Touch.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • Mit all diesen Tools können Sie prüfen, ob Ihre App unabhängig vom Eingabegerät des Nutzers ordnungsgemäß funktioniert.

  • Hoffentlich haben Sie jetzt eine bessere Vorstellung davon, wie ein Zeigerereignis von einem Finger auf einem Touchpad bis zu Ihrer App gelangt.

  • Und jetzt übergebe ich an Jeremy, der über eine ganz andere Art der Verwendung von Trackpads sprechen wird.

Einführung in die indirekte Eingabe

  • Vielen Dank.

  • Okay.

  • Hallo, ich bin Jeremy Walker.

  • Ich arbeite viel an Eingaben und dachte, es wäre interessant, Ihnen einige der neuen Dinge zu zeigen, die wir mit Compose-Eingaben machen, und wie sich das auf Sie auswirkt.

  • Und insbesondere mit diesem indirekten Eingabetyp, der durch eine neue Eingabe basierend auf einem neuen Formfaktor entstanden ist: intelligente Brillen.

  • Wenn Sie also Eingaben von einer Brille erhalten, können Sie das auf verschiedene Arten tun: Sie können etwas ansehen, Ihre Stimme verwenden, Gesten verwenden, aber ich werde speziell über das Touchpad sprechen.

  • Diese unterscheiden sich ein wenig von denen, über die Alex gesprochen hat. Sie befinden sich an der Seite des Kopfes, sind vertikal, sehr breit und extrem kurz.

  • Als wir uns überlegt haben, wie das in Compose funktionieren könnte, haben wir uns vorgestellt, dass man einen kleinen Button vor sich sieht und einen Cursor dorthin bewegen muss, um ihn zu berühren. Das wäre keine gute Nutzererfahrung, oder?

  • Wie werden wir es also unterstützen?

  • Zur Erinnerung: So sieht die Benutzeroberfläche der Brille aus.

  • Sie ist so konzipiert, dass Inhalte nahtlos in dein Sichtfeld eingeblendet werden.

  • Aber es ist sehr einfach, wir versuchen, es einfach zu halten.

  • Hier sehen Sie ein Beispiel für eine Liste.

  • Es ist auf ein bestimmtes Element in der Mitte fokussiert.

  • Was wollen wir wirklich tun?

  • Wir möchten es auswählen oder vielleicht zum vorherigen oder nächsten Element wechseln.

  • Wir benötigen nicht unbedingt eine Bildschirmposition.

  • Diese Option ist etwas komplizierter, aber Sie sehen, dass es im Grunde drei Optionen gibt: Sie können auflegen, die Stummschaltung aufheben oder das Video deaktivieren.

  • Aber eigentlich möchten Sie es auswählen oder zu einem anderen wechseln und dieses auswählen.

  • Wir denken also, dass wir nicht unbedingt direkt eine Position auf dem Bildschirm oder in diesem Fall auf den Augen zuordnen müssen.

  • So sieht das also aus.

  • Hier ist ein Beispiel: Ich bewege mich zwischen Elementen auf der Benutzeroberfläche der Brille und das entspricht in etwa dem, was wir wollen.

  • Und du kannst darauf tippen.

  • Wir haben uns Android angesehen, um herauszufinden, was es da draußen gibt, das wir dafür nutzen könnten.

  • Und wenn Sie jemals… Oh, Entschuldigung.

  • Hier wird ein Schritt übersprungen.

  • Ich muss noch einmal zurückgehen.

  • Na also.

  • Okay.

  • Wenn ihr schon einmal eines dieser Geräte verwendet und mit einem Android-Gerät verknüpft habt, wisst ihr, dass ihr ganz einfach von einem UI-Element zum anderen wechseln könnt.

  • Wenn du die Fernbedienung mit Google TV verknüpft hast und dir eine ganze Reihe von Filmen angezeigt wird, kannst du mit den Pfeiltasten zum gewünschten Film navigieren und ihn mit der OK-Taste auswählen.

  • Wenn Sie ein Spielmenü haben und diesen Controller verwenden, können Sie mit dem Steuerkreuz oder dem Joystick zu einem UI-Element, z. B. in einem Menü, navigieren und es auswählen.

  • Das Gleiche gilt für die Tastatur.

  • Das entspricht also in etwa dem, was wir in Compose erreichen möchten.

  • Hier sind noch einmal die Dinge, damit Sie sich ein Bild davon machen können, wie das aussieht.

  • Und wenn wir noch etwas weiter zurückgehen, gab es tatsächlich diese Art von Touchpad, das mit einem Fernseher gekoppelt war und eine sogenannte Navigation per Berührung verwendete.

  • Die Idee dahinter ist, dass die X- und Y-Koordinaten überhaupt nicht mit dem Bildschirm verbunden sind.

  • Sie werden nur für die Gestenerkennung verwendet.

  • Wenn Sie nach rechts wischen, ist das so, als würden Sie auf den Pfeil tippen, um weiterzugehen.

  • Wenn Sie nach links wischen, bewegen Sie sich nach links, oben und unten.

  • Das ist also im Großen und Ganzen das, was wir wollen.

  • Sie verwenden x und y nur für das Geschwindigkeits-Tracking, um die Zieh- und Wischbewegungen usw. zu ermitteln. Damit haben wir das meiste, was wir für den Einstieg in Compose benötigen.

  • Das ist nicht ganz genau, weil es irgendwie dem Fernsehen zugeordnet ist. Wir müssen also darauf achten, dass wir nicht den Weg des Fernsehens gehen und später nicht die Fernseher durcheinanderbringen. Wir müssen also darauf achten, dass wir diese aufteilen, aber darauf kommen wir gleich noch zu sprechen.

  • Aber es ist ein guter Ausgangspunkt.

  • Damit wir diese Art der Touch-Navigation verwenden können.

API-Design für indirekte Eingabe und neue Formfaktoren

  • Okay.

  • Wie wird das API-Design aussehen?

  • In Compose ist die allgemeinste oder häufigste Eingabe die Berührung, oder?

  • Wir haben also viel Arbeit vor uns.

  • Wir mussten also herausfinden, wie es damit zusammenhängt.

  • Zur Erinnerung: So sieht das PointerEvent aus.

  • Hier sehen Sie den Typ, der die Abwärtsbewegung in der Aufwärtsbewegung ist, und dann haben Sie diesen PointerInputChange, der nur eine Liste ist. Sie können sich das als Finger vorstellen.

  • Wenn Sie drei Finger unten haben, stehen drei Dinge auf der Liste.

  • Wir wollten also, dass es so aussieht.

  • Dies ist kein UI-Modul, das ein viel niedrigeres Modul ist als die meisten Entwickler verwenden, wenn sie mit Eingaben arbeiten.

  • Sie befinden sich eine Ebene über der Foundation-Ebene.

  • Und dort befinden sich alle unsere Gestendetektoren.

  • Denn als Entwickler interessieren Sie sich nicht für all diese kleinen Eingaben.

  • Sie möchten nur wissen, ob ein Ziehen, ein Klicken oder ein Wischen stattgefunden hat, und dann Ihre Aktion darauf basierend ausführen.

  • Wir haben also eine Menge Code, der das für Sie erledigt, und der wird im Hintergrund von diesen Funktionen unterstützt.

  • Wir haben uns also überlegt, wie wir es so einfach wie möglich machen können, dass der Code in Foundation diese neuen Typen unterstützt.

  • Also haben wir einen neuen Typ erstellt, der ziemlich ähnlich aussieht.

  • Sie wird nur als indirekt bezeichnet.

  • Und die indirekte Bedeutung ist, dass es sich nicht um eine direkte Berührung des Displays handelt.

  • So weißt du, dass du dich nicht auf die Touch-Bedienung konzentrieren musst.

  • Wir kümmern uns hier nur um die Gestenverarbeitung und die Navigation zwischen UI-Elementen.

  • Der Typ ist derselbe.

  • Es ist eine Liste mit Fingern.

  • In diesem Fall ist es „IndirectPointerInputChange“.

  • Wir haben das also gemacht und es hat super funktioniert.

  • Dann sind wir auf einen weiteren Punkt gestoßen: die primäre Richtungsbewegungsachse.

  • Das ist ein ziemlicher Zungenbrecher, aber es wird Sinn ergeben, wenn ich es erkläre, denn es ist ziemlich interessant, wie es dazu kam.

  • Wie ist es dazu gekommen?

  • Nun, Sie haben dieses Touchpad an der Seite Ihres Kopfes, und es ist breit und kurz.

  • An diesem Punkt hatten wir also die Idee, dass wir eine Touch-Navigation haben.

  • Wir können also nach links und rechts wischen.

  • Das funktioniert gut.

  • Aber wenn man sich die heutigen Trackpads ansieht,

  • Wenn Sie nach links oder rechts wischen, bewegen Sie sich auf dem Trackpad mit Ihren UI-Inhalten nach links oder rechts.

  • Wenn Sie nach oben oder unten wischen, scrollen Sie in der Liste nach oben oder unten.

  • Wenn Sie das mit einer Brille versuchen, ist das nicht so schlimm, oder?

  • Aber wenn Sie das tun, ist das keine gute Erfahrung.

  • Wir haben festgestellt, dass die natürlichste Bewegung neben dem Tippen das Wischen an der Seite der Brille ist.

  • Wir haben eine Reihe von Studien mit Nutzern durchgeführt und festgestellt, dass sie tatsächlich erwarten, dass der Inhalt beim horizontalen Wischen nicht horizontal, sondern vertikal verschoben wird.

  • Sie können sich das so vorstellen: Wenn Sie hier wischen und eine Liste vor sich haben, können Sie in der Liste vorwärts oder rückwärts gehen.

  • Wir nehmen also eine zweidimensionale Eingabe und versuchen, sie eindimensional zu betrachten.

  • Und so entstand die Idee für die primäre Bewegungsachse: Du kannst horizontal wischen und es bewegt sich vertikal.

  • Du kannst also auf dem Gerät selbst festlegen,

  • Du kannst sagen: „Hey, das ist kein normales Trackpad.“

  • Ich möchte, dass die primäre Richtung horizontal ist. Das ist die einzige Achse, entlang der ich mich bewegen möchte.

  • Daher kommt das.

  • Okay.

  • Wir sind also ganz zufrieden.

  • Compose kümmert sich um alles.

  • Und Sie haben diese schöne vertikale Implementierung.

  • Sie können sich das so vorstellen:

  • Bei der horizontalen Definition gibt es nur „Vorwärts“ und „Rückwärts“.

  • Es gibt kein Oben, Unten, Links oder Rechts.

  • Wir haben nur „Vorwärts“ und „Zurück“.

  • Und dann können wir das zuordnen, wenn Sie mit dem Fokus auf „Weiter“ und „Zurück“ vertraut sind.

  • Jetzt können wir einfach zum nächsten und vorherigen Element wechseln.

  • Das funktioniert auch gut für vertikale Videos.

  • Und es ist auch noch schöner.

  • Es funktioniert standardmäßig mit horizontalen Daten, oder?

  • Denn Sie haben nur eine Reihe von fokussierten Elementen.

  • Du kannst einfach zum nächsten oder vorherigen Titel springen und es funktioniert mit beiden.

  • Das ist ein Fall, in dem wir das alles in Compose definiert und herausgefunden haben. Es wäre aber wirklich gut, wenn das Gerät selbst das sagen könnte, weil es weiß, ob es eine ungewöhnliche Form hat und ob es nur diese primäre Achse möchte, aber es hat keine Möglichkeit, das zu definieren.

  • Wir mussten dieses Konzept also zurück zur Plattform bringen und sagen: Okay, wir müssen das in der Plattform definieren, damit wir es standardisieren können und diese Geräte es selbst angeben können, damit Compose es dann vollständig unterstützen kann.

  • In diesem Fall haben wir also in Compose gearbeitet, sind in Compose vorangekommen, haben rückwärts gearbeitet und die Ergebnisse mitgenommen.

  • Genau das haben wir getan.

  • Wir haben diese neue API erstellt, damit Sie oder das Gerät dies angeben können.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • Wie ich bereits erwähnt habe, ist die Eingabe per Berührung bzw. die Touch-Navigation hauptsächlich für Fernseher gedacht.

  • Wir wollten diese Implementierungen nicht beeinträchtigen.

  • Deshalb haben wir ein weiteres Feld hinzugefügt, um diese neue Art der Glaseingabe anzugeben.

  • Okay.

  • Was bedeutet das also für Sie?

  • Dies ist eine spezifischere Version des Gestendetektors.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • Wenn Sie Foundation verwenden, funktioniert es sofort mit den Gestendetektoren.

  • Wir haben die Arbeit für Sie erledigt, Sie müssen also nichts weiter tun.

  • Wenn Sie jedoch eine App für die Brille entwickeln möchten, können Sie unsere Glimmer-Bibliothek verwenden. Wir haben einen speziellen Gestendetektor, der Ihnen nur die Gesten liefert, sodass Sie sie nicht kombinieren müssen. Sie können einfach diesen einen verwenden, um den Klick, den Vorwärts- und den Rückwärtsframe zu erhalten.

Indirekte Zeigereingabe testen

  • Okay.

  • Wir haben uns also die Funktionsweise der API in der Plattform und in Compose angesehen.

  • Wie sieht es mit Tests aus?

  • So sieht der Touchsensor-Test aus.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • Du sagst einfach performTouchInput und machst dann eine Geste.

  • Es gibt viele verschiedene Gesten und wir bauen alle kleinen Ereignisse auf, um die Geste zu ermöglichen.

  • Wie funktioniert das im Großen und Ganzen?

  • Sie haben das Compose-UI-Modul und das UI-Testmodul, die zumindest eingabeseitig über MotionEvents kommunizieren.

  • Das UI-Testmodul verwendet außerdem einen Warteschlangenmechanismus, der auf MotionEvent basiert.

  • Warum erzähle ich Ihnen das?

  • Denn man kann ein MotionEvent, das einem indirekten Eingabeereignis ähnelt, nicht programmatisch erstellen. Das war toll für mich, denn es bedeutete, dass ich die gesamte Kommunikation zwischen den Modulen und dem Warteschlangensystem neu schreiben musste.

  • Aber ich habe es geschafft.

  • Was bedeutet das für Sie?

  • Wenn Sie an Tests für die Eingabe per Berührung gewöhnt sind, ist dies das Ergebnis für die indirekte Eingabe.

  • Sie fügen dem Namen einfach „indirekt“ hinzu.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput (
    IndirectPointerEventPrimaryDirectionalMotionAxis.X,
    inputDeviceSize,
    )
    { click(center) }
    }
  • Ich habe einige zusätzliche Parameter übergeben.

  • Die erste Option kennen Sie wahrscheinlich. Hier geben Sie die horizontale Position an.

  • Die Eingabegröße wäre dann natürlich die Größe des Touchpads.

  • Das ändert nicht viel.

  • Wenn Sie sich den Quellcode ansehen, sehen Sie, dass ich eine Erweiterungsfunktion geschrieben habe, die so aussieht.

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

  • Das sollte Ihnen eine Vorstellung davon geben, wie viel Arbeit wir investiert haben, um Touchpads auf Ihren Rahmen oder Smart Glasses zu unterstützen.

  • Aber wir machen diese Übungen auch für das Augen- und Handtracking, also kommt noch viel mehr.

  • Was möchte ich also, dass Sie sich von meinem Teil merken?

Zusammenfassung und wichtigste Erkenntnisse

  • Egal, ob Sie ein Trackpad mit einem Smartphone, einer Brille oder einem anderen Eingabegerät verwenden, wir denken darüber nach und versuchen, die Arbeit im Voraus zu erledigen.

  • Wenn Sie also diese Foundation-Gesten-Detektoren verwenden, funktioniert es sofort.

  • Das ist der erste Punkt.

  • Zweitens ist Compose das primäre UI-Toolkit. Wir werden weiterhin neue Eingabetypen sowohl auf der Plattform als auch im Toolkit untersuchen und unterstützen, wie Sie hier sehen können.

  • Und für das Testen von APIs möchte ich natürlich eine vertraute Oberfläche und ein vertrautes Paradigma beibehalten, damit Sie diese neuen Typen so einfach wie möglich migrieren und testen können.

  • Vielen Dank. Ich hoffe, ich konnte Ihnen weiterhelfen.

Code

Code-Snippets

Kotlin-Snippet bei 15:47 in „Trackpad-Updates und Touchgesten mit mehreren Fingern“
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
Kotlin-Snippet bei 25:08 in „API-Design für indirekte Eingabe und neue Formfaktoren“
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
Kotlin-Snippet bei 25:32 in „API-Design für indirekte Eingabe und neue Formfaktoren“
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
Kotlin-Snippet bei 26:11 in „Testing indirect pointer input“
@Test
clickTest {

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

}
Kotlin-Snippet bei 27:08 in „Testing indirect pointer input“
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
Kotlin-Snippet bei 27:23 in „Testing indirect pointer input“
@Test
clickTest {

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

}