Android und ChromeOS bieten eine Vielzahl von APIs, mit denen Sie Apps erstellen können, die Ihren Nutzern einen außergewöhnlichen Eingabestift bieten. Die Klasse MotionEvent
liefert Informationen zur Interaktion eines Eingabestifts mit dem Bildschirm, einschließlich Druck des Eingabestifts, Ausrichtung, Neigung, Mauszeiger und Handflächenerkennung. Grafiken mit niedriger Latenz und Bibliotheken für Bewegungsvorhersagen verbessern das Rendering von Eingabestiften auf dem Bildschirm, um ein natürliches Erscheinungsbild wie mit einem Stift und Papier zu ermöglichen.
MotionEvent
Die Klasse MotionEvent
repräsentiert Interaktionen mit Nutzereingaben wie die Position und Bewegung der Touchpointer auf dem Bildschirm. Bei Eingabe über einen Eingabestift stellt MotionEvent
auch Daten zu Druck, Ausrichtung, Neigung und dem Mauszeiger bereit.
Ereignisdaten
Fügen Sie den Komponenten einen pointerInput
-Modifikator hinzu, um auf MotionEvent
-Daten zuzugreifen:
@Composable
fun Greeting() {
Text(
text = "Hello, Android!", textAlign = TextAlign.Center, style = TextStyle(fontSize = 5.em),
modifier = Modifier
.pointerInput(Unit) {
awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { println(it) }
}
}
},
)
}
Ein MotionEvent
-Objekt stellt Daten zu den folgenden Aspekten eines UI-Ereignisses bereit:
- Aktionen: Physische Interaktion mit dem Gerät – Berühren des Bildschirms, Bewegen eines Mauszeigers über die Bildschirmoberfläche, Bewegen eines Zeigers über die Bildschirmoberfläche
- Zeiger: Kennzeichnungen von Objekten, die mit dem Bildschirm interagieren – Finger, Eingabestift, Maus
- Achse: Datentyp – x- und y-Koordinaten, Druck, Neigung, Ausrichtung und Hover (Entfernung)
Aktionen
Damit Sie die Unterstützung für Eingabestifte implementieren können, müssen Sie verstehen, welche Aktion der Nutzer ausführt.
MotionEvent
bietet eine Vielzahl von ACTION
-Konstanten, die Bewegungsereignisse definieren. Die wichtigsten Aktionen für den Eingabestift:
Aktion | Beschreibung |
---|---|
ACTION_DOWN ACTION_POINTER_DOWN |
Der Zeiger hat den Bildschirm berührt. |
ACTION_MOVE (Aktion_VERSCHIEBEN) | Zeiger bewegt sich auf dem Bildschirm. |
ACTION_UP ACTION_POINTER_UP |
Zeiger berührt den Bildschirm nicht mehr |
AKTION_ABBRECHEN | Wann der vorherige oder die aktuelle Bewegungssatz abgebrochen werden soll. |
Deine App kann Aufgaben wie das Starten eines neuen Strichs, wenn ACTION_DOWN
auftritt, den Strich mit ACTION_MOVE,
zeichnen und ihn beenden, wenn ACTION_UP
ausgelöst wird.
Die Gruppe der MotionEvent
-Aktionen von ACTION_DOWN
bis ACTION_UP
für einen bestimmten Zeiger wird als Bewegungssatz bezeichnet.
Mauszeiger
Die meisten Bildschirme sind Multi-Touch-Funktionen: Das System weist jedem Finger, Eingabestift, jeder Maus und jedem anderen Zeigeobjekt, das mit dem Bildschirm interagiert, einen Zeiger zu. Mit einem Zeigerindex können Sie Achseninformationen für einen bestimmten Zeiger abrufen, z. B. die Position des ersten Fingers, der den Bildschirm berührt, oder der Position des zweiten Fingers.
Zeigerindexe reichen von null bis zur Anzahl der Zeiger, die von MotionEvent#pointerCount()
abzüglich 1 zurückgegeben werden.
Auf die Achsenwerte der Zeiger kann mit der Methode getAxisValue(axis,
pointerIndex)
zugegriffen werden.
Wenn der Zeigerindex weggelassen wird, gibt das System den Wert für den ersten Zeiger zurück, Zeiger Null (0).
MotionEvent
-Objekte enthalten Informationen zum verwendeten Zeigertyp. Sie können den Zeigertyp abrufen, indem Sie die Zeigerindexe durchlaufen und die Methode getToolType(pointerIndex)
aufrufen.
Weitere Informationen zu Zeigern finden Sie unter Multi-Touch-Gesten verarbeiten.
Eingabestift
Mit TOOL_TYPE_STYLUS
können Sie nach Eingabestiften filtern:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Der Eingabestift kann auch melden, dass er als Radierer verwendet wird, mit TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Daten der Eingabestiftachse
ACTION_DOWN
und ACTION_MOVE
stellen die Achsendaten des Eingabestifts bereit, also x- und y-Koordinaten, Druck, Ausrichtung, Neigung und Mauszeiger.
Die MotionEvent
API stellt getAxisValue(int)
bereit, um den Zugriff auf diese Daten zu ermöglichen. Dabei steht der Parameter für eine der folgenden Achsenkennungen:
Axis | Rückgabewert von getAxisValue() |
---|---|
AXIS_X |
X-Koordinate eines Bewegungsereignisses |
AXIS_Y |
Y-Koordinate eines Bewegungsereignisses |
AXIS_PRESSURE |
Auf einem Touchscreen oder Touchpad der Druck, der von einem Finger, einem Eingabestift oder einem anderen Zeiger ausgeübt wird. Bei Maus- oder Trackballs: 1, wenn die Haupttaste gedrückt wird, andernfalls 0. |
AXIS_ORIENTATION |
Bei einem Touchscreen oder Touchpad die Ausrichtung eines Fingers, eines Eingabestifts oder eines anderen Zeigers relativ zur vertikalen Ebene des Geräts. |
AXIS_TILT |
Der Neigungswinkel des Eingabestifts im Bogenmaß. |
AXIS_DISTANCE |
Der Abstand des Eingabestifts zum Bildschirm. |
MotionEvent.getAxisValue(AXIS_X)
gibt beispielsweise die X-Koordinate für den ersten Zeiger zurück.
Weitere Informationen finden Sie unter Multi-Touch-Gesten bedienen.
Position
Sie können die x- und y-Koordinaten eines Zeigers mit den folgenden Aufrufen abrufen:
MotionEvent#getAxisValue(AXIS_X)
oderMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
oderMotionEvent#getY()
Luftdruck
Sie können den Druck des Zeigers mit MotionEvent#getAxisValue(AXIS_PRESSURE)
oder mit MotionEvent#getPressure()
für den ersten Zeiger abrufen.
Der Druckwert für Touchscreens oder Touchpads liegt zwischen 0 (kein Druck) und 1. Je nach Bildschirmkalibrierung können aber auch höhere Werte zurückgegeben werden.
Ausrichtung
Die Ausrichtung gibt an, in welche Richtung der Eingabestift zeigt.
Die Zeigerausrichtung kann mit getAxisValue(AXIS_ORIENTATION)
oder getOrientation()
(für den ersten Zeiger) abgerufen werden.
Bei einem Eingabestift wird die Ausrichtung als Radiantwert zwischen 0 und pi (∂) im Uhrzeigersinn oder 0 bis -pi gegen den Uhrzeigersinn zurückgegeben.
Mithilfe der Ausrichtung kannst du einen realen Pinsel implementieren. Wenn der Eingabestift beispielsweise einen flachen Pinsel darstellt, hängt seine Breite von seiner Ausrichtung ab.
Neigen
„Neigung“ gibt die Neigung des Eingabestifts relativ zum Bildschirm an.
Die Neigung gibt den positiven Winkel des Eingabestifts im Bogenmaß zurück, wobei Null senkrecht zum Bildschirm und ∂/2 flach auf der Oberfläche ist.
Der Neigungswinkel kann mit getAxisValue(AXIS_TILT)
abgerufen werden (keine Verknüpfung für den ersten Zeiger).
Mithilfe der Neigung können reale Werkzeuge so nah wie möglich reproduziert werden, z. B. die Schattierung mit einem geneigten Bleistift nachzuahmen.
Mauszeiger hierher bewegen
Die Entfernung des Eingabestifts zum Bildschirm kann mit getAxisValue(AXIS_DISTANCE)
abgerufen werden. Die Methode gibt einen Wert von 0,0 (Kontakt mit dem Bildschirm) zu höheren Werten zurück, wenn sich der Eingabestift vom Bildschirm entfernt. Der Abstand zwischen dem Bildschirm und der Spitze des Eingabestifts hängt vom Hersteller des Bildschirms und des Eingabestifts ab. Da Implementierungen variieren können, verlassen Sie sich für anwendungskritische Funktionen nicht auf präzise Werte.
Wenn du den Eingabestift bewegst, kannst du eine Vorschau der Größe des Pinsels ansehen oder angeben, dass eine Schaltfläche ausgewählt wird.
Hinweis:Die Funktion „Compose“ bietet Modifikatoren, die sich auf den interaktiven Status von UI-Elementen auswirken:
hoverable
: Konfigurieren Sie die Komponente so, dass sie mit Zeiger-Eingabe- und -Exit-Ereignissen bewegt werden kann.indication
: Zeichnet visuelle Effekte für diese Komponente bei Interaktionen.
Ablehnen des Palms, Navigation und unerwünschte Eingaben
Manchmal werden unerwünschte Berührungen registriert, z. B. wenn ein Nutzer seine Hand auf dem Bildschirm ablehnt, um ihn beim Schreiben zu unterstützen.
Die Palm-Ablehnung ist ein Mechanismus, der dieses Verhalten erkennt und Sie darüber informiert, dass der letzte MotionEvent
-Satz abgebrochen werden soll.
Daher müssen Sie einen Verlauf der Nutzereingaben speichern, damit die unerwünschten Berührungen vom Bildschirm entfernt und die legitimen Nutzereingaben neu gerendert werden können.
ACTION_CANCEL und FLAG_CANCELED
ACTION_CANCEL
und FLAG_CANCELED
sollen Sie darüber informieren, dass der vorherige MotionEvent
-Satz vom letzten ACTION_DOWN
abgebrochen werden soll. So können Sie beispielsweise den letzten Strich für eine Zeichen-App für einen bestimmten Zeiger rückgängig machen.
AKTION_ABBRECHEN
In Android 1.0 (API-Level 1) hinzugefügt
ACTION_CANCEL
gibt an, dass der vorherige Satz von Bewegungsereignissen abgebrochen werden soll.
ACTION_CANCEL
wird ausgelöst, wenn eines der folgenden Elemente erkannt wird:
- Touch-Gesten für die Navigation
- Ablehnung der Handfläche
Wenn ACTION_CANCEL
ausgelöst wird, sollten Sie den aktiven Cursor mit getPointerId(getActionIndex())
identifizieren. Entfernen Sie dann den mit diesem Zeiger erstellten Strich aus dem Eingabeverlauf und rendern Sie die Szene erneut.
FLAG_ABGEBROCHEN
In Android 13 (API-Level 33) hinzugefügt
FLAG_CANCELED
gibt an, dass der nach oben gerichtete Zeiger eine unbeabsichtigte Berührung des Nutzers war. Das Flag wird normalerweise gesetzt, wenn der Nutzer versehentlich den Bildschirm berührt, z. B. wenn er das Gerät hält oder die Handfläche auf den Bildschirm legt.
So greifen Sie auf den Flag-Wert zu:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Wenn das Flag gesetzt ist, müssen Sie die letzte Einstellung für MotionEvent
, ausgehend vom letzten ACTION_DOWN
von diesem Zeiger, rückgängig machen.
Wie ACTION_CANCEL
kann der Zeiger mit getPointerId(actionIndex)
gefunden werden.
Vollbild-, Rand-zu-Rand- und Navigationsgesten
Wenn eine App im Vollbildmodus ist und interaktive Elemente am Rand enthält, wie z. B. den Canvas einer Zeichen- oder Notiz-App, kann das Wischen vom unteren Bildschirmrand zum Aufrufen der Navigation oder zum Verschieben der App in den Hintergrund zu einer unerwünschten Berührung des Canvas führen.
Um zu verhindern, dass durch Gesten unerwünschte Berührungen in deiner App ausgelöst werden, kannst du Einsätze und ACTION_CANCEL
nutzen.
Weitere Informationen finden Sie im Abschnitt Palmenablehnung, Navigation und unerwünschte Eingaben.
Mit der Methode setSystemBarsBehavior()
und BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
von WindowInsetsController
kannst du verhindern, dass Navigationsgesten unerwünschte Touch-Ereignisse verursachen:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Weitere Informationen zur Verwaltung von Einfügungen und Touch-Gesten finden Sie unter:
- Systemleisten für immersiven Modus ausblenden
- Kompatibilität mit der Bedienung über Gesten gewährleisten
- Edge-to-Edge-Inhalte in der App präsentieren
Niedrige Latenz
Die Latenz ist die Zeit, die Hardware, System und Anwendung für die Verarbeitung und das Rendern von Nutzereingaben benötigen.
Latenz = Eingabeverarbeitung von Hardware und Betriebssystem + App-Verarbeitung + Zusammensetzung des Systems
- Hardware-Rendering
Quelle der Latenz
- Eingabestift bei Touchscreen registrieren (Hardware): Erste kabellose Verbindung, wenn der Eingabestift und das Betriebssystem kommunizieren, um registriert und synchronisiert zu werden.
- Rate der Berührungsabtastung (Hardware): Die Häufigkeit, mit der ein Touchscreen pro Sekunde prüft, ob ein Zeiger die Oberfläche berührt. Der Bereich liegt zwischen 60 und 1.000 Hz.
- Eingabeverarbeitung (App): Anwenden von Farben, grafischen Effekten und Transformationen auf Nutzereingaben.
- Grafikrendering (Betriebssystem und Hardware): Pufferaustausch, Hardwareverarbeitung
Grafik mit niedriger Latenz
Die Jetpack-Grafikbibliothek mit niedriger Latenz verkürzt die Verarbeitungszeit zwischen Nutzereingabe und Bildschirmrendering.
Die Bibliothek reduziert die Verarbeitungszeit, indem sie das Multi-Zwischenspeicher-Rendering vermeidet und eine Front-Puffer-Rendering-Technik nutzt, was bedeutet, dass direkt auf den Bildschirm geschrieben wird.
Frontbuffer-Rendering
Der Front-Zwischenspeicher ist der Arbeitsspeicher, den der Bildschirm für das Rendern verwendet. Es ist die beste Möglichkeit, Apps direkt auf dem Bildschirm zu zeichnen. Die Bibliothek mit niedriger Latenz ermöglicht es Apps, direkt im Front-Zwischenspeicher zu rendern. Dies verbessert die Leistung, indem verhindert wird, dass Zwischenspeicher gewechselt wird, was bei einem regulären Rendering mit mehreren Zwischenspeichern oder beim Rendering mit Doppelpuffern vorkommen kann (dies ist der häufigste Fall).
Obwohl das Frontpuffer-Rendering eine gute Technik zum Rendern eines kleinen Bereichs des Bildschirms ist, ist es nicht für die Aktualisierung des gesamten Bildschirms vorgesehen. Beim Frontbuffer-Rendering rendert die App Inhalte in einem Zwischenspeicher, aus dem die Anzeige liest. Dies kann zum Rendern oder Reißen von Artefakten führen(siehe unten).
Die Bibliothek mit niedriger Latenz ist ab Android 10 (API-Level 29) und auf ChromeOS-Geräten mit Android 10 (API-Level 29) und höher verfügbar.
Abhängigkeiten
Die Bibliothek mit niedriger Latenz stellt die Komponenten für die Front-Zwischenspeicher-Rendering-Implementierung bereit. Die Bibliothek wird als Abhängigkeit in der Moduldatei build.gradle
der Anwendung hinzugefügt:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
GLFrontBufferRenderer-Callbacks
Die Bibliothek mit niedriger Latenz enthält die Schnittstelle GLFrontBufferRenderer.Callback
, die die folgenden Methoden definiert:
Die Bibliothek mit niedriger Latenz ist hinsichtlich der Art von Daten, die Sie mit GLFrontBufferRenderer
verwenden, nicht unvoreingenommen.
Die Bibliothek verarbeitet die Daten jedoch als einen Strom aus Hunderten von Datenpunkten. Daher sollten Sie Ihre Daten so gestalten, dass die Arbeitsspeichernutzung und -zuweisung optimiert wird.
Rückrufe
Implementieren Sie GLFrontBufferedRenderer.Callback
und überschreiben Sie onDrawFrontBufferedLayer()
und onDrawDoubleBufferedLayer()
, um Rendering-Callbacks zu aktivieren.
GLFrontBufferedRenderer
verwendet die Callbacks, um die Daten optimal zu rendern.
val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {
override fun onDrawFrontBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
param: DATA_TYPE
) {
// OpenGL for front buffer, short, affecting small area of the screen.
}
override fun onDrawMultiDoubleBufferedLayer(
eglManager: EGLManager,
bufferInfo: BufferInfo,
transform: FloatArray,
params: Collection<DATA_TYPE>
) {
// OpenGL full scene rendering.
}
}
Instanz von GLFrontBufferedRenderer deklarieren
Bereiten Sie das GLFrontBufferedRenderer
vor. Geben Sie dazu das SurfaceView
und die Callbacks an, die Sie zuvor erstellt haben. GLFrontBufferedRenderer
optimiert das Rendering im Front- und Double-Zwischenspeicher mithilfe Ihrer Callbacks:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Rendering
Das Front-Zwischenspeicher-Rendering beginnt, wenn Sie die Methode renderFrontBufferedLayer()
aufrufen, wodurch der onDrawFrontBufferedLayer()
-Callback ausgelöst wird.
Das Rendering mit doppelter Zwischenspeicherung wird fortgesetzt, wenn Sie die Funktion commit()
aufrufen, die den Callback onDrawMultiDoubleBufferedLayer()
auslöst.
Im folgenden Beispiel wird der Prozess in den Front-Zwischenspeicher (schnelles Rendering) gerendert, wenn der Nutzer beginnt, auf dem Bildschirm zu zeichnen (ACTION_DOWN
) und den Zeiger umherbewegt (ACTION_MOVE
). Der Prozess wird in den doppelten Zwischenspeicher gerendert, wenn der Zeiger die Oberfläche des Bildschirms verlässt (ACTION_UP
).
Mit requestUnbufferedDispatch()
können Sie festlegen, dass das Eingabesystem keine Bewegungsereignisse in Batches aufnimmt, sondern sie liefert, sobald sie verfügbar sind:
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
// Deliver input events as soon as they arrive.
view.requestUnbufferedDispatch(motionEvent)
// Pointer is in contact with the screen.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_MOVE -> {
// Pointer is moving.
glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
}
MotionEvent.ACTION_UP -> {
// Pointer is not in contact in the screen.
glFrontBufferRenderer.commit()
}
MotionEvent.CANCEL -> {
// Cancel front buffer; remove last motion set from the screen.
glFrontBufferRenderer.cancel()
}
}
Tipps zum Rendern
Kleine Bildschirmbereiche, Handschrift, Zeichnen, Skizzieren
Vollbildaktualisierung, Schwenken, Zoomen. Dies kann zum Zerreißen führen.
Reißend
Das Zerreißen erfolgt, wenn der Bildschirm aktualisiert wird, während der Bildschirmzwischenspeicher gleichzeitig geändert wird. Ein Teil des Bildschirms zeigt neue Daten, ein anderer zeigt alte Daten.
Bewegungsvorhersage
Die Jetpack-Bewegungsvorhersagebibliothek reduziert die wahrgenommene Latenz, da der Strichpfad des Nutzers geschätzt und dem Renderer temporäre, künstliche Punkte bereitgestellt werden.
Die Bibliothek für Bewegungsvorhersagen erhält echte Nutzereingaben als MotionEvent
-Objekte.
Die Objekte enthalten Informationen über x- und y-Koordinaten, den Druck und die Zeit, die vom Bewegungsvorhersager verwendet werden, um zukünftige MotionEvent
-Objekte vorherzusagen.
Vorhergesagte MotionEvent
-Objekte sind nur Schätzungen. Vorhergesagte Ereignisse können die empfundene Latenz reduzieren, aber vorhergesagte Daten müssen nach dem Empfang durch tatsächliche MotionEvent
-Daten ersetzt werden.
Die Bibliothek für Bewegungsvorhersagen ist ab Android 4.4 (API-Level 19) und auf ChromeOS-Geräten mit Android 9 (API-Level 28) und höher verfügbar.
Abhängigkeiten
Die Bibliothek für die Bewegungsvorhersage implementiert die Vorhersagefunktionen. Die Bibliothek wird als Abhängigkeit in der Moduldatei build.gradle
der Anwendung hinzugefügt:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementierung
Die Bewegungsvorhersagebibliothek enthält die MotionEventPredictor
-Schnittstelle, mit der die folgenden Methoden definiert werden:
record()
: SpeichertMotionEvent
-Objekte als Datensatz der Nutzeraktionenpredict()
: gibt eine vorhergesagteMotionEvent
zurück
Instanz von MotionEventPredictor
deklarieren
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Den Predictor mit Daten versorgen
motionEventPredictor.record(motionEvent)
Vorhersage
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
Tipps zur Bewegungsvorhersage
Entfernen Sie Vorhersagepunkte, wenn ein neuer vorhergesagter Punkt hinzugefügt wird.
Verwenden Sie für das endgültige Rendering keine Vorhersagepunkte.
Notizen-Apps
Über ChromeOS können in deiner App bestimmte Aktionen zum Erstellen von Notizen deklariert werden.
Informationen zum Registrieren einer App als Notizen-App unter ChromeOS finden Sie unter Eingabekompatibilität.
Informationen zum Registrieren einer App als Notizen-App auf Android finden Sie unter Notizen-App erstellen.
Mit Android 14 (API-Level 34) wurde der Intent ACTION_CREATE_NOTE
eingeführt, mit dem Ihre App Notizen auf dem Sperrbildschirm starten kann.
Digitale Tintenerkennung mit ML Kit
Mit der digitalen Tintenerkennung von ML Kit kann Ihre App handschriftlichen Text in Hunderten von Sprachen auf einer digitalen Oberfläche erkennen. Sie können auch Skizzen klassifizieren.
ML Kit bietet die Klasse Ink.Stroke.Builder
zum Erstellen von Ink
-Objekten, die von Modellen für maschinelles Lernen verarbeitet werden können, um Handschrift in Text umzuwandeln.
Zusätzlich zur Handschrifterkennung kann das Modell Gesten wie Löschen und Einkreisen erkennen.
Weitere Informationen finden Sie unter Digitale Tintenerkennung.