Es gibt mehrere Begriffe und Konzepte, die Sie verstehen sollten wenn Sie an der Gestenhandhabung in einer Anwendung arbeiten. Auf dieser Seite werden die Nutzungsbedingungen Zeigerereignisse und Gesten und stellt die verschiedenen Ebenen für Gesten. Außerdem werden die Ereignisdaten und Verbreitung von Inhalten.
Definitionen
Um die verschiedenen Konzepte auf dieser Seite zu verstehen, müssen Sie einige der verwendeten Terminologie:
- Zeiger: Ein physisches Objekt, über das Sie mit Ihrer Anwendung interagieren können.
Bei Mobilgeräten ist der Zeiger am häufigsten, dass Ihr Finger mit
Touchscreen verwenden. Alternativ kannst du einen Eingabestift verwenden, um deinen Finger zu ersetzen.
Bei großen Bildschirmen können Sie über eine Maus oder ein Touchpad indirekt mit
Display. Ein Eingabegerät muss in der Lage sein, auf "verweisen" zu können. auf einer Koordinate
sodass z. B. eine Tastatur nicht als
Zeiger. In Compose wird der Zeigertyp mithilfe von
PointerType
- Zeigerereignis: Beschreibt eine Interaktion auf niedriger Ebene mit einem oder mehreren Zeigern.
mit der Anwendung zu einem bestimmten Zeitpunkt. Jede Zeigerinteraktion, wie z. B. Putting
einen Finger auf dem Bildschirm zeigt
oder die Maus ziehen, löst ein Ereignis aus. In
Alle relevanten Informationen für ein solches Ereignis sind in der
Klasse
PointerEvent
. - Geste: Eine Folge von Zeigerereignissen, die als ein einzelnes Aktion ausführen. Tippgesten können z. B. als Abfolge eines Abwärtsspiels betrachtet werden. gefolgt von einem "up"-Ereignis. Es gibt häufige Gesten, die von vielen zum Beispiel durch Tippen, Ziehen oder Umwandeln. Sie können aber auch wenn nötig.
Verschiedene Abstraktionsebenen
Jetpack Compose bietet verschiedene Abstraktionsebenen für die Verarbeitung von Gesten.
Ganz oben befindet sich der Support für Komponenten. Zusammensetzbare Elemente wie Button
werden automatisch Gesten unterstützt. Unterstützung für Touch-Gesten zu benutzerdefinierten
Komponenten lassen sich beliebige Gesten-Modifikatoren wie clickable
zusammensetzbare Funktionen verwenden. Wenn Sie eine benutzerdefinierte Touch-Geste benötigen, können Sie den
pointerInput
-Modifikator.
Nutzen Sie in der Regel die höchste Abstraktionsebene,
Funktionen, die Sie benötigen. So profitieren Sie von den
im Layer. Beispielsweise enthält Button
weitere semantische Informationen, die für
als clickable
, die mehr Informationen enthält als eine unbearbeitete
pointerInput
-Implementierung.
Komponentenunterstützung
Viele vorkonfigurierte Komponenten in Compose enthalten eine Art interne Geste
Umgang mit Ihren Daten. Ein LazyColumn
reagiert beispielsweise auf Ziehgesten wie folgt:
Wenn Sie durch den Inhalt scrollen, erscheint eine Button
mit einer Welle, wenn Sie auf das Display drücken.
und die Komponente SwipeToDismiss
eine Wischlogik zum Schließen eines
-Elements. Diese Art der Gestenhandhabung funktioniert automatisch.
Neben der internen Gestensteuerung muss der Anrufer bei vielen Komponenten
um die Geste auszuführen. Ein Button
erkennt beispielsweise automatisch
und löst ein Klickereignis aus. Du übergibst ein Lambda von onClick
an Button
, um
auf die Geste reagieren. Genauso fügst du einem Lambda onValueChange
zu einem
Slider
, um zu reagieren, wenn der Nutzer den Schieberegler zieht.
Je nach Anwendungsfall sollten Sie Gesten, die in Komponenten enthalten sind,
sofort einsatzbereite Unterstützung für Fokus und Barrierefreiheit, und sie sind
getestet. Ein Button
ist beispielsweise auf spezielle Weise markiert, sodass
Bedienungshilfen beschreiben sie korrekt als Schaltfläche und nicht einfach
anklickbares Element:
// Talkback: "Click me!, Button, double tap to activate" Button(onClick = { /* TODO */ }) { Text("Click me!") } // Talkback: "Click me!, double tap to activate" Box(Modifier.clickable { /* TODO */ }) { Text("Click me!") }
Weitere Informationen zu den Bedienungshilfen in der Funktion „Schreiben“ finden Sie unter Bedienungshilfen in Schreiben:
Beliebigen zusammensetzbaren Funktionen mit Modifikatoren bestimmte Touch-Gesten hinzufügen
Sie können Bewegungsmodifikatoren auf jede beliebige zusammensetzbare Funktion anwenden, um
zusammensetzbare Funktionen hören auf Gesten. Sie können beispielsweise eine allgemeine Box
Tippgesten mit clickable
oder mit einem Column
verarbeiten.
vertikales Scrollen durch Anwenden von verticalScroll
verarbeiten können.
Es gibt viele Modifikatoren, um verschiedene Arten von Gesten zu verarbeiten:
- Halte das Tippen und Drücken mit der
clickable
,combinedClickable
,selectable
,toggleable
undtriStateToggleable
-Modifikatoren. - Scrollen mit
horizontalScroll
,verticalScroll
und allgemeinerescrollable
-Modifikatoren. - Ziehpunkt: mit
draggable
undswipeable
Modifikator. - Multi-Touch-Gesten wie Schwenken, Drehen und Zoomen mit
den
transformable
-Modifikator.
In der Regel sollten Sie vorgefertigte Modifikatoren für Touch-Gesten statt benutzerdefinierter Touch-Gesten verwenden.
Die Modifikatoren bieten mehr Funktionen zusätzlich zur Verarbeitung reiner Zeigerereignisse.
Der Modifikator clickable
fügt beispielsweise nicht nur die Erkennung von Drücken und
semantische Informationen, visuelle Hinweise auf Interaktionen,
Mausbewegung, Fokus und Tastaturunterstützung. Sie können den Quellcode
von clickable
, um zu sehen,
wird hinzugefügt.
Beliebigen zusammensetzbaren Funktionen mit dem pointerInput
-Modifikator benutzerdefinierte Touch-Gesten hinzufügen
Nicht jede Geste wird mit einem vorkonfigurierten Bewegungsmodifikator implementiert. Für
Sie können z. B. keinen Modifikator verwenden, um auf Ziehbewegungen zu reagieren,
Strg-Klick oder mit drei Fingern tippen. Du kannst stattdessen deine eigene Touch-Geste schreiben
um diese benutzerdefinierten Gesten zu identifizieren. Sie können einen Gesten-Handler mit
den pointerInput
-Modifikator, mit dem Sie auf den Rohzeiger zugreifen können
Ereignisse.
Mit dem folgenden Code werden rohe Pointer-Ereignisse überwacht:
@Composable private fun LogPointerEvents(filter: PointerEventType? = null) { var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(filter) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() // handle pointer event if (filter == null || event.type == filter) { log = "${event.type}, ${event.changes.first().position}" } } } } ) } }
Wenn Sie dieses Snippet aufteilen, sind die Hauptkomponenten:
- Der
pointerInput
-Modifikator. Sie übergeben ihm einen oder mehrere Schlüssel. Wenn der Parameter ändert sich der Wert eines dieser Schlüssel, ist das Lambda für den Modifikatorinhalt erneut ausgeführt werden. Im Beispiel wird ein optionaler Filter an die zusammensetzbare Funktion übergeben. Wenn ändert sich der Wert dieses Filters, sollte der Zeiger-Event-Handler um sicherzustellen, dass die richtigen Ereignisse protokolliert werden. awaitPointerEventScope
erstellt einen Koroutinebereich, mit dem Sie Folgendes tun können: auf Zeigerereignisse warten.awaitPointerEvent
unterbricht die Koroutine bis zum nächsten Zeigerereignis erfolgt.
Das Abhören von Roheingabeereignissen ist zwar leistungsstark, es ist jedoch auch schwierig, eine benutzerdefinierte Touch-Geste basierend auf diesen Rohdaten. Um das Erstellen benutzerdefinierter stehen viele Dienstprogrammmethoden zur Verfügung.
Vollständige Gesten erkennen
Anstatt die Rohzeigerereignisse zu verarbeiten, können Sie auf bestimmte Gesten warten.
und entsprechend reagieren. Die AwaitPointerEventScope
bietet
Methoden zum Überwachen von:
- Drücken, tippen, doppeltippen und lange drücken:
detectTapGestures
- Drag-and-drop:
detectHorizontalDragGestures
,detectVerticalDragGestures
,detectDragGestures
unddetectDragGesturesAfterLongPress
- Transformationen:
detectTransformGestures
Dies sind Detektoren der obersten Ebene. Es können also nicht mehrere Detektoren innerhalb eines einzelnen Detektors hinzugefügt werden.
pointerInput
-Modifikator. Das folgende Snippet erkennt nur die Berührungen, nicht
Drags:
var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(Unit) { detectTapGestures { log = "Tap!" } // Never reached detectDragGestures { _, _ -> log = "Dragging" } } ) }
Intern blockiert die Methode detectTapGestures
die Koroutine und die zweite
Detektor wird nie erreicht. Wenn Sie mehr als einen Gesten-Listener für
eine zusammensetzbare Funktion. Verwenden Sie stattdessen separate pointerInput
-Modifikatorinstanzen:
var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(Unit) { detectTapGestures { log = "Tap!" } } .pointerInput(Unit) { // These drag events will correctly be triggered detectDragGestures { _, _ -> log = "Dragging" } } ) }
Ereignisse pro Geste verarbeiten
Gesten beginnen per Definition mit einem Zeiger-nach-unten-Ereignis. Sie können die
awaitEachGesture
-Hilfsmethode anstelle der while(true)
-Schleife, die
durchläuft jedes Rohereignis. Mit der Methode awaitEachGesture
wird der
enthält einen Block, wenn alle Zeiger angehoben wurden. Dies zeigt an, dass die Geste
Abgeschlossen:
@Composable private fun SimpleClickable(onClick: () -> Unit) { Box( Modifier .size(100.dp) .pointerInput(onClick) { awaitEachGesture { awaitFirstDown().also { it.consume() } val up = waitForUpOrCancellation() if (up != null) { up.consume() onClick() } } } ) }
In der Praxis empfiehlt es sich, awaitEachGesture
zu verwenden, es sei denn,
auf Zeigerereignisse zu reagieren, ohne Gesten zu erkennen. Ein Beispiel:
hoverable
, die nicht auf Zeiger- oder Aufwärts-Ereignisse reagiert, sondern nur
muss wissen, wann ein Zeiger in seine Grenzen eintritt oder ihn verlässt.
Auf ein bestimmtes Ereignis oder eine bestimmte Bewegung warten
Es gibt eine Reihe von Methoden, mit denen häufige Teile von Gesten identifiziert werden können:
- Anhalten, bis ein Zeiger mit
awaitFirstDown
ausfällt, oder warten, bis alle um mitwaitForUpOrCancellation
nach oben zu gehen. - Erstellen Sie mit
awaitTouchSlopOrCancellation
einen Low-Level-Drag-Listener. undawaitDragOrCancellation
. Der Gesten-Handler wird erst angehalten, erreicht der Zeiger den Touch-Slop und hält dann an, bis das erste Drag-Ereignis durch. Wenn Sie nur an Ziehpunkten entlang einer Achse interessiert sind, ÜberawaitHorizontalTouchSlopOrCancellation
awaitHorizontalDragOrCancellation
oderawaitVerticalTouchSlopOrCancellation
plusawaitVerticalDragOrCancellation
. - In Verbindung mit
awaitLongPressOrCancellation
sperren, bis ein langes Drücken erfolgt. - Verwenden Sie die Methode
drag
, um fortlaufend auf Drag-Events zu warten, oderhorizontalDrag
oderverticalDrag
, um Drag-Events auf einem Gerät zu beobachten .
Berechnungen für Multi-Touchpoint-Ereignisse anwenden
Führt ein Nutzer eine Multi-Touch-Geste mit mehr als einem Zeiger aus,
ist es komplex, die erforderliche Transformation
auf Grundlage der Rohwerte zu verstehen.
Wenn der transformable
-Modifikator oder der detectTransformGestures
nicht genügend differenzierte Kontrolle über Ihren Anwendungsfall bieten, können Sie
auf Rohdaten warten und
Berechnungen darauf anwenden. Diese Hilfsmethoden
sind calculateCentroid
, calculateCentroidSize
,
calculatePan
, calculateRotation
und calculateZoom
.
Ereignisweiterleitung und Treffertests
Nicht jedes Zeigerereignis wird an jeden pointerInput
-Modifikator gesendet. Ereignis
-Weiterleitung funktioniert so:
- Zeigerereignisse werden in einer zusammensetzbaren Hierarchie abgefertigt. Der Moment, in dem ein neuer Zeiger sein erstes Zeigerereignis auslöst, beginnt das System mit den Treffertests. „Aktiv“ zusammensetzbaren Funktionen. Eine zusammensetzbare Funktion gilt als aktiv, wenn Verarbeitungsfunktionen für die Zeigereingabe. Treffertestabläufe vom oberen Rand der Benutzeroberfläche Baum nach unten. Eine zusammensetzbare Funktion heißt „Treffer“ Zeitpunkt des Zeigerereignisses innerhalb der Grenzen dieser zusammensetzbaren Funktion. Dieser Prozess führt zu einer Kette zusammensetzbaren Funktionen, bei denen Treffertests erfolgreich sind.
- Wenn mehrere zusammensetzbare Funktionen auf derselben Ebene
Baum ist nur die zusammensetzbare Funktion mit dem höchsten Z-Index "hit". Für
Wenn Sie beispielsweise zwei zusammensetzbare Funktionen vom Typ
Button
zu einerBox
hinzufügen, die sich überschneiden, Die darüber gezeichnete Karte empfängt Zeigerereignisse. Sie können theoretisch können Sie dieses Verhalten überschreiben, indem Sie eine eigenePointerInputModifierNode
erstellen. undsharePointerInputWithSiblings
auf „true“ setzen. - Weitere Ereignisse für denselben Zeiger werden an dieselbe Kette von zusammensetzbare Funktionen erstellt und die Daten gemäß der Logik für die Ereignisweitergabe verarbeitet werden. Das System führt für diesen Zeiger keine weiteren Treffertests durch. Das bedeutet, dass jeder zusammensetzbare Funktion in der Kette empfängt alle Ereignisse für diesen Zeiger, auch wenn die außerhalb der Grenzen der zusammensetzbaren Funktion liegen. Zusammensetzbare Elemente, die nicht in der Kette nie Zeigerereignisse erhalten, auch wenn der Zeiger innerhalb ihrer Grenzen.
Mouseover-Ereignisse, die durch Bewegen der Maus oder eines Eingabestifts ausgelöst werden, stellen eine Ausnahme von die hier definierten Regeln an. Hover-Ereignisse werden an jede zusammensetzbare Funktion gesendet, die sie treffen. Also wenn ein Nutzer mit der Maus auf einen Zeiger von den Grenzen einer zusammensetzbaren Funktion zur nächsten bewegt, statt sie an die erste zusammensetzbare Funktion zu senden, neu zusammensetzbar.
Ereignisnutzung
Ist mehr als einer zusammensetzbaren Funktion ein Gesten-Handler zugewiesen, werden diese keine Konflikte zwischen Handlern verursachen. Sehen Sie sich zum Beispiel diese UI an:
Wenn ein Nutzer auf die Lesezeichenschaltfläche tippt, übernimmt die Lambda-Funktion onClick
der Schaltfläche dies
Touch-Geste. Wenn ein Nutzer auf einen anderen Teil des Listeneintrags tippt, wird die ListItem
diese Geste verarbeitet und
zum entsprechenden Artikel navigiert. Was die Zeigereingabe betrifft,
Die Schaltfläche muss dieses Ereignis nutzen, damit das übergeordnete Element weiß, dass dies nicht geschieht.
nicht mehr darauf reagieren können. Gesten, die in den vorgefertigten Komponenten integriert sind, und
Gängige Bewegungsmodifikatoren beinhalten dieses Verhalten.
Ihre eigene benutzerdefinierte Geste schreiben, müssen Sie die Ereignisse manuell verarbeiten. Sie tun das
mit der Methode PointerInputChange.consume
:
Modifier.pointerInput(Unit) { awaitEachGesture { while (true) { val event = awaitPointerEvent() // consume all changes event.changes.forEach { it.consume() } } } }
Die Übernahme eines Ereignisses beendet nicht die Weitergabe des Ereignisses an andere zusammensetzbare Funktionen. A Die zusammensetzbaren Funktionen müssen stattdessen verbrauchte Ereignisse explizit ignorieren. Beim Schreiben können Sie prüfen, ob ein Ereignis bereits von einem anderen :
Modifier.pointerInput(Unit) { awaitEachGesture { while (true) { val event = awaitPointerEvent() if (event.changes.any { it.isConsumed }) { // A pointer is consumed by another gesture handler } else { // Handle unconsumed event } } } }
Ereignisweitergabe
Wie bereits erwähnt, werden Zeigeränderungen an jede zusammensetzbare Funktion übergeben, auf die sie trifft.
Wenn jedoch mehr als eine dieser zusammensetzbaren Funktionen vorhanden ist, in welcher Reihenfolge
verbreiten? Wenn Sie das Beispiel aus dem letzten Abschnitt nehmen,
im folgenden UI-Baum, in dem nur ListItem
und Button
auf
Zeigerereignisse:
Zeigerereignisse fließen dreimal durch jede dieser zusammensetzbaren Funktionen. "passes":
- In der ersten Karte wird das Ereignis vom oberen Rand des UI-Baums an den
unten. Bei diesem Ablauf kann ein übergeordnetes Element ein Ereignis abfangen, bevor das untergeordnete Element es
konsumieren. Beispielsweise müssen Kurzinfos einen
lange drücken, anstatt sie an ihr Kind weiterzugeben. In unserem
Beispiel:
ListItem
empfängt das Ereignis vorButton
. - In der Hauptkarte fließt das Ereignis von den Blattknoten der Benutzeroberfläche bis zum
Stamm des UI-Baums. In dieser Phase nutzt du normalerweise Gesten.
die Standardkarte für die Überwachung von Ereignissen. Gesten in dieser Karte bzw. diesem Ticket verarbeiten
Blattknoten haben Vorrang vor ihren übergeordneten Knoten, d. h.
Verhaltens für die meisten Gesten ist. In unserem Beispiel erhält die
Button
das Ereignis vorListItem
. - In der endgültigen Karte läuft das Ereignis noch einmal vom oberen Rand der Benutzeroberfläche aus. bis zu den Blattknoten. Durch diesen Ablauf können Elemente weiter oben im Stapel auf die Ereignisdaten durch die übergeordneten Elemente reagieren. Über eine Schaltfläche werden beispielsweise Wenn ein Drücken in einen Ziehpunkt des scrollbaren übergeordneten Elements verwandelt wird, wird die Wellenform angezeigt.
Der Ereignisfluss kann folgendermaßen visuell dargestellt werden:
Sobald eine Eingabeänderung verarbeitet wurde, werden diese Informationen von dieser Punkt im Ablauf ab:
Im Code kannst du die gewünschte Karte bzw. das gewünschte Ticket angeben:
Modifier.pointerInput(Unit) { awaitPointerEventScope { val eventOnInitialPass = awaitPointerEvent(PointerEventPass.Initial) val eventOnMainPass = awaitPointerEvent(PointerEventPass.Main) // default val eventOnFinalPass = awaitPointerEvent(PointerEventPass.Final) } }
In diesem Code-Snippet wird dasselbe identische Ereignis Aufrufe der Await-Methode, obwohl die Daten zum Verbrauch geändert.
Touch-Gesten testen
In Ihren Testmethoden können Sie manuell Zeigerereignisse mit der Methode
performTouchInput
-Methode. So können Sie entweder
auf höherer Ebene
Touch-Gesten wie Auseinander- und Zusammenziehen oder langes Klicken oder Bewegungen auf niedriger Ebene, z. B.
den Cursor um eine bestimmte Anzahl von Pixeln bewegen):
composeTestRule.onNodeWithTag("MyList").performTouchInput { swipeUp() swipeDown() click() }
Weitere Beispiele finden Sie in der Dokumentation zu performTouchInput
.
Weitere Informationen
Weitere Informationen zu Touch-Gesten in Jetpack Compose findest du hier: Ressourcen:
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Bedienungshilfen in „Compose“
- Scrollen
- Tippen und drücken