Android e ChromeOS forniscono una serie di API per aiutarti a creare app che offrono
agli utenti un'esperienza con lo stilo eccezionale. La
MotionEvent
del corso espone
informazioni sull'interazione dello stilo con lo schermo, inclusa la pressione dello stilo,
orientamento, inclinazione, passaggio del mouse e rilevamento del palmo. Grafica e movimento a bassa latenza
le librerie di previsione migliorano il rendering sullo schermo con lo stilo per fornire
naturale, simile a carta e penna.
MotionEvent
La classe MotionEvent
rappresenta le interazioni di input dell'utente, come la posizione
e il movimento dei puntatori touch sullo schermo. Per l'inserimento con lo stilo, MotionEvent
espone anche dati su pressione, orientamento, inclinazione e passaggio del mouse.
Dati sugli eventi
Per accedere ai dati di MotionEvent
, aggiungi un modificatore pointerInput
ai componenti:
@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) }
}
}
},
)
}
Un oggetto MotionEvent
fornisce dati relativi ai seguenti aspetti di una UI
dell'evento:
- Azioni: interazione fisica con il dispositivo, tocco dello schermo, Spostamento di un puntatore sulla superficie dello schermo, passaggio del puntatore sopra lo schermo piattaforma
- Puntatori: identificatori degli oggetti che interagiscono con lo schermo: dita, stilo, mouse
- Asse: tipo di dati, come coordinate x e y, pressione, inclinazione, orientamento, e passa il mouse sopra (distanza)
Azioni
Per implementare il supporto dello stilo, devi sapere quale azione sta svolgendo l'utente l'efficienza operativa.
MotionEvent
fornisce un'ampia gamma di costanti ACTION
che definiscono il movimento
eventi. Le azioni più importanti per lo stilo includono:
Azione | Descrizione |
---|---|
AZIONE_GIÙ AZIONE_POINTER_GIÙ |
Il puntatore ha stabilito un contatto con lo schermo. |
SPOSTA_AZIONE | Il puntatore si sta muovendo sullo schermo. |
ACTION_UP ACTION_POINTER_UP |
Il puntatore non è più in contatto con lo schermo |
ANNULLA AZIONE | Quando il movimento precedente o corrente deve essere annullato. |
La tua app può eseguire attività come iniziare un nuovo stile quando ACTION_DOWN
accade, tracciando il tratto con ACTION_MOVE,
e terminando il tratto quando
ACTION_UP
viene attivato.
L'insieme di MotionEvent
azioni da ACTION_DOWN
a ACTION_UP
per un determinato
un puntatore è un insieme di movimento.
Puntatori
La maggior parte delle schermate è multi-touch: il sistema assegna un puntatore a ciascun dito, stilo, mouse o un altro oggetto di punta che interagisce con lo schermo. Un puntatore consente di ottenere le informazioni dell'asse per un puntatore specifico, come posizione del primo dito che tocca lo schermo o il secondo.
Gli indici dei puntatori vanno da zero al numero di puntatori restituiti da
MotionEvent#pointerCount()
meno 1.
È possibile accedere ai valori dell'asse dei puntatori con il metodo getAxisValue(axis,
pointerIndex)
.
Quando l'indice del puntatore viene omesso, il sistema restituisce il valore per il primo
puntatore zero (0).
Gli oggetti MotionEvent
contengono informazioni sul tipo di puntatore in uso. Tu
può ottenere il tipo di puntatore iterando gli indici dei cursori e chiamando
il
getToolType(pointerIndex)
.
Per ulteriori informazioni sui puntatori, consulta Gestire il multi-touch gesti.
Input stilo
Puoi filtrare in base agli input dello stilo con
TOOL_TYPE_STYLUS
:
val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)
Lo stilo può anche segnalare che viene usato come gomma con
TOOL_TYPE_ERASER
:
val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)
Dati asse stilo
ACTION_DOWN
e ACTION_MOVE
forniscono i dati degli assi relativi allo stilo, ovvero x e
Coordinate y, pressione, orientamento, inclinazione e passaggio del mouse.
Per abilitare l'accesso a questi dati, l'API MotionEvent
fornisce
getAxisValue(int)
,
in cui il parametro è uno dei seguenti identificatori dell'asse:
Axis | Valore restituito di getAxisValue() |
---|---|
AXIS_X |
Coordinata X di un evento di movimento. |
AXIS_Y |
Coordinata Y di un evento di movimento. |
AXIS_PRESSURE |
Per un touchscreen o un touchpad, la pressione applicata da un dito, uno stilo o un altro puntatore. Per un mouse o una trackball, 1 se viene premuto il pulsante principale, 0 in caso contrario. |
AXIS_ORIENTATION |
Per un touchscreen o un touchpad, l'orientamento di un dito, dello stilo o di un altro puntatore rispetto al piano verticale del dispositivo. |
AXIS_TILT |
L'angolo di inclinazione dello stilo espresso in radianti. |
AXIS_DISTANCE |
La distanza dello stilo dallo schermo. |
Ad esempio, MotionEvent.getAxisValue(AXIS_X)
restituisce la coordinata x per la
primo puntatore.
Vedi anche Gestire il multi-touch gesti.
Posizione
Puoi recuperare le coordinate x e y di un puntatore con le seguenti chiamate:
MotionEvent#getAxisValue(AXIS_X)
oppureMotionEvent#getX()
MotionEvent#getAxisValue(AXIS_Y)
oppureMotionEvent#getY()
![Disegno con lo stilo sullo schermo con le coordinate X e Y mappate.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_x_y_coordinates.png?authuser=9&hl=it)
Pressione
Puoi recuperare la pressione del puntatore con
MotionEvent#getAxisValue(AXIS_PRESSURE)
o, per il primo puntatore,
MotionEvent#getPressure()
Il valore della pressione per i touchscreen o i touchpad è un valore compreso tra 0 (nessuna pressione) e 1, ma è possibile restituire valori più alti a seconda dello schermo calibrazione.
![Stilo che rappresenta un continuum di pressione da bassa ad alta. L'tratto è stretto e debole a sinistra, ad indicare una bassa pressione. Il tratto diventa più ampio e scuro da sinistra a destra fino a quando non diventa più largo e più scuro all'estrema destra, indicando la pressione più alta.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_pressure.png?authuser=9&hl=it)
Orientamento
L'orientamento indica la direzione in cui è rivolto lo stilo.
L'orientamento del puntatore può essere recuperato utilizzando getAxisValue(AXIS_ORIENTATION)
o
getOrientation()
(per il primo puntatore).
Per uno stilo, l'orientamento viene restituito come un valore in radianti compreso tra 0 e pi greco (p) in senso orario o da 0 a -pi in senso antiorario.
L'orientamento consente di implementare un pennello reale. Ad esempio, se lo stilo rappresenta un pennello piatto, la sua larghezza dipende dal orientamento stilo.
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_orientation.png?authuser=9&hl=it)
Inclinazione
L'inclinazione misura l'inclinazione dello stilo rispetto allo schermo.
L'inclinazione restituisce l'angolo positivo dello stilo in radianti, dove zero è perpendicolare allo schermo e p/2 è piatto sulla superficie.
L'angolo di inclinazione può essere recuperato utilizzando getAxisValue(AXIS_TILT)
(nessuna scorciatoia per
il primo puntatore).
L'inclinazione può essere utilizzata per riprodurre strumenti reali il più vicino possibile, come che riproduce l'ombreggiatura con una matita inclinata.
![Stilo inclinato di circa 40 gradi dalla superficie dello schermo.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_tilt.png?authuser=9&hl=it)
Passaci il mouse sopra
La distanza dello stilo dallo schermo può essere ottenuta con
getAxisValue(AXIS_DISTANCE)
. Il metodo restituisce un valore compreso tra 0,0 (contatto con
sullo schermo) a valori più alti quando lo stilo si allontana dallo schermo. Il passaggio del mouse
la distanza tra lo schermo e il pennino (punto) dello stilo dipende
produttore di schermo e stilo. Poiché le implementazioni possono
variano, non fare affidamento su valori precisi per le funzionalità critiche dell'app.
Puoi utilizzare lo stilo per visualizzare in anteprima le dimensioni del pennello o indicare che viene selezionato.
![](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_hover.png?authuser=9&hl=it)
Nota:Compose fornisce modificatori che influiscono sullo stato interattivo degli elementi dell'interfaccia utente:
hoverable
: configura il componente per il passaggio del mouse utilizzando gli eventi di entrata e uscita del puntatore.indication
: consente di disegnare effetti visivi per questo componente quando si verificano delle interazioni.
Rifiuto del palmo della mano, navigazione e input indesiderati
A volte gli schermi multi-touch possono registrare tocchi indesiderati, ad esempio quando
l'utente appoggia naturalmente la mano sullo schermo per avere un supporto durante la scrittura a mano libera.
Il rifiuto del palmo della mano è un meccanismo che rileva questo comportamento e ti avvisa che
l'ultima impostazione di MotionEvent
deve essere annullata.
Di conseguenza, devi conservare una cronologia degli input dell'utente in modo che gli tocchi indesiderati possono essere rimossi dallo schermo e gli input legittimi dell'utente possono essere di nuovo sottoposti a rendering.
ACTION_CANCEL e FLAG_CANCELED
ACTION_CANCEL
e
FLAG_CANCELED
sono
entrambi pensati per informarti che il precedente set di MotionEvent
dovrebbe essere
annullato dall'ultimo ACTION_DOWN
, in modo da poter, ad esempio, annullare l'ultimo
tratto per un'app di disegno di un determinato puntatore.
ANNULLA AZIONE
Aggiunte in Android 1.0 (livello API 1)
ACTION_CANCEL
indica che la serie di eventi di movimento precedente deve essere annullata.
ACTION_CANCEL
viene attivato quando viene rilevata una delle seguenti condizioni:
- Gesti di navigazione
- Rifiuto del palmo della mano
Quando ACTION_CANCEL
viene attivato, devi identificare il puntatore attivo con
getPointerId(getActionIndex())
. Quindi rimuovi il tratto creato con quel puntatore dalla cronologia di input ed esegui nuovamente il rendering della scena.
SEGNALAZIONE_ANNULLATA
Aggiunte in Android 13 (livello API 33)
FLAG_CANCELED
indica che il puntatore salito era un tocco involontario dell'utente. Il flag è
in genere viene impostata quando l'utente tocca accidentalmente lo schermo, ad esempio afferrando
il dispositivo o posizionando il palmo della mano sullo schermo.
Per accedere al valore del flag:
val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED
Se il flag è impostato, devi annullare l'ultima impostazione di MotionEvent
, dall'ultima
ACTION_DOWN
da questo puntatore.
Come ACTION_CANCEL
, il puntatore si trova con getPointerId(actionIndex)
.
MotionEvent
. Il palmo della mano è stato annullato e il display viene sottoposto di nuovo a rendering.
Gesti a schermo intero, edge-to-edge e di navigazione
Se un'app è a schermo intero e presenta elementi interattivi vicino al bordo, come area di un'app per disegnare o prendere appunti, che scorre dalla parte inferiore dello schermo per visualizzare la navigazione o spostare l'app in background potrebbe un tocco indesiderato sulla tela.
Per evitare che i gesti attivino tocchi indesiderati nella tua app, puoi:
e sfrutta gli insiemi
ACTION_CANCEL
.
Consulta anche la sezione Rifiuto di un palmo, navigazione e input indesiderati .
Utilizza la
setSystemBarsBehavior()
e
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
di
WindowInsetsController
per evitare che i gesti di navigazione provochino eventi tocco indesiderati:
// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
Per scoprire di più sulla gestione dei riquadri e dei gesti, vedi:
- Nascondi le barre di sistema per la modalità immersiva
- Garantire la compatibilità con la navigazione tramite gesti
- Visualizza i contenuti in modo edge-to-edge nell'app
Bassa latenza
La latenza è il tempo richiesto dall'hardware, dal sistema e dall'applicazione per elaborare ed eseguire il rendering dell'input dell'utente.
Latenza = elaborazione input hardware e sistema operativo + elaborazione app + compositing del sistema
- rendering hardware
![La latenza causa un ritardo rispetto alla posizione dello stilo per il tratto sottoposto a rendering. Lo spazio tra il tratto sottoposto a rendering e la posizione dello stilo rappresenta la latenza.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_latency.png?authuser=9&hl=it)
Origine della latenza
- Registrazione stilo con touchscreen (hardware): connessione wireless iniziale quando lo stilo e il sistema operativo comunicano per essere registrati e sincronizzati.
- Frequenza di campionamento del tocco (hardware): il numero di volte al secondo per un touchscreen verifica se un puntatore tocca la superficie, con valori compresi tra 60 e 1000 Hz.
- Elaborazione dell'input (app): applicazione di colore, effetti grafici e trasformazione sull'input dell'utente.
- Rendering grafico (sistema operativo + hardware): scambio del buffer, elaborazione hardware.
Grafica a bassa latenza
La libreria grafica a bassa latenza di Jetpack riduce i tempi di elaborazione tra l'input dell'utente e il rendering sullo schermo.
La libreria riduce i tempi di elaborazione evitando il rendering multi-buffer e sfruttando una tecnica di rendering con buffer frontale, che significa scrivere direttamente sullo schermo.
Rendering buffer frontale
Il buffer anteriore è la memoria utilizzata dallo schermo per il rendering. È il più vicino possono disegnare direttamente sullo schermo. La libreria a bassa latenza consente di cui eseguire il rendering direttamente nel buffer frontale. Questo migliora il rendimento Impedisce lo scambio di buffer, cosa che può verificarsi nel normale rendering multi-buffer o rendering a doppio buffer (il caso più comune).
![L'app scrive nel buffer dello schermo e legge dal buffer.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/front_buffer_rendering.png?authuser=9&hl=it)
![L'app scrive sul buffer multi-buffer, che viene scambiato con il buffer dello schermo. L'app legge dal buffer dello schermo.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/multi-buffer_rendering.png?authuser=9&hl=it)
Il rendering con buffer frontale è un'ottima tecnica per eseguire il rendering di una piccola area schermo, non è progettato per essere utilizzato per aggiornare l'intera schermata. Con front-buffer, l'app sta eseguendo il rendering dei contenuti in un buffer da cui che il display sta leggendo. Di conseguenza, c'è la possibilità di eseguire il rendering artefatti o lo strappo (vedi di seguito).
La libreria a bassa latenza è disponibile da Android 10 (livello API 29) e versioni successive e sui dispositivi ChromeOS con Android 10 (livello API 29) e versioni successive.
Dipendenze
La libreria a bassa latenza fornisce i componenti per il rendering con buffer frontale
implementazione. La libreria viene aggiunta come dipendenza nel modulo dell'app
File build.gradle
:
dependencies {
implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}
Callback di GLFrontBufferRenderer
La libreria a bassa latenza include
GLFrontBufferRenderer.Callback
che definisce i seguenti metodi:
La libreria a bassa latenza non ha un presupposto per il tipo di dati che utilizzi
GLFrontBufferRenderer
.
Tuttavia, la libreria elabora i dati come un flusso di centinaia di punti dati; Progettare i dati in modo da ottimizzare l'utilizzo e l'allocazione della memoria.
Callback
Per attivare i callback di rendering, implementa GLFrontBufferedRenderer.Callback
e
eseguire l'override di onDrawFrontBufferedLayer()
e onDrawDoubleBufferedLayer()
.
GLFrontBufferedRenderer
utilizza i callback per eseguire il rendering dei dati
ottimizzato possibile.
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.
}
}
Dichiara un'istanza di GLFrontBufferedRenderer
Prepara GLFrontBufferedRenderer
fornendo i SurfaceView
e
che hai creato in precedenza. GLFrontBufferedRenderer
ottimizza il rendering
all'inizio e nel doppio buffer usando i callback:
var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
Rendering
Il rendering del buffer frontale si avvia quando chiami il metodo
renderFrontBufferedLayer()
che attiva il callback onDrawFrontBufferedLayer()
.
Il rendering a doppio buffer riprende quando chiami il metodo
commit()
che attiva il callback onDrawMultiDoubleBufferedLayer()
.
Nell'esempio che segue, il processo viene eseguito nel front buffer (rapido
rendering) quando l'utente inizia a disegnare sullo schermo (ACTION_DOWN
) e si sposta
il puntatore intorno a (ACTION_MOVE
). Il processo viene eseguito sul doppio buffer
quando il puntatore esce dalla superficie dello schermo (ACTION_UP
).
Puoi utilizzare la modalità
requestUnbufferedDispatch()
per chiedere che il sistema di input non raggruppa gli eventi di movimento, ma fornisca
non appena sono disponibili:
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()
}
}
Cosa fare e cosa non fare durante il rendering
Piccole parti dello schermo, scrittura a mano libera, disegno o schizzi.
Aggiornamento a schermo intero, panoramica, zoom. Può causare strappi.
Strappo
Si verifica uno strappo quando lo schermo si aggiorna mentre è in corso il buffer dello schermo modificati contemporaneamente. Una parte dello schermo mostra nuovi dati, mentre un'altra mostra i vecchi dati.
![Le parti superiore e inferiore dell'immagine Android sono disallineate a causa dello strappo durante l'aggiornamento dello schermo.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/tearing.png?authuser=9&hl=it)
Previsione del movimento
La previsione del movimento di Jetpack libreria riduce latenza percepita stimando il percorso dell'utente e fornendo di punti artificiali al renderer.
La libreria di previsioni dei movimenti riceve input utente reali come oggetti MotionEvent
.
Gli oggetti contengono informazioni su coordinate x e y, pressione e tempo,
sfruttati dal predittore di movimento per prevedere MotionEvent
futuri
di oggetti strutturati.
Gli oggetti MotionEvent
previsti sono solo stime. Gli eventi previsti possono ridurre
latenza percepita, ma i dati previsti devono essere sostituiti con MotionEvent
effettivi
una volta ricevuti.
La libreria di previsione dei movimenti è disponibile a partire da Android 4.4 (livello API 19) e e su dispositivi ChromeOS con Android 9 (livello API 28) e versioni successive.
![La latenza causa un ritardo rispetto alla posizione dello stilo per il tratto sottoposto a rendering. Lo spazio tra il tratto e lo stilo è pieno di punti di previsione. Il divario rimanente è la latenza percepita.](https://developer.android.google.cn/static/images/develop/ui/compose/touch-input/stylus-input/advanced-stylus/stylus_reduced_latency.png?authuser=9&hl=it)
Dipendenze
La libreria di previsione del movimento fornisce l'implementazione della previsione. La
la libreria viene aggiunta come dipendenza nel file del modulo build.gradle
dell'app:
dependencies {
implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}
Implementazione
La libreria di previsione del movimento include
MotionEventPredictor
che definisce i seguenti metodi:
record()
: ArchiviaMotionEvent
oggetti come record delle azioni dell'utentepredict()
: Restituisce un valoreMotionEvent
previsto
Dichiara un'istanza di MotionEventPredictor
var motionEventPredictor = MotionEventPredictor.newInstance(view)
Fornisci dati al predittore
motionEventPredictor.record(motionEvent)
Previsione
when (motionEvent.action) {
MotionEvent.ACTION_MOVE -> {
val predictedMotionEvent = motionEventPredictor?.predict()
if(predictedMotionEvent != null) {
// use predicted MotionEvent to inject a new artificial point
}
}
}
Cosa fare e cosa non fare con la previsione del movimento
Rimuovi i punti di previsione quando viene aggiunto un nuovo punto previsto.
Non utilizzare punti di previsione per il rendering finale.
App per creare note
ChromeOS consente alla tua app di dichiarare alcune azioni per prendere appunti.
Per registrare un'app come app per creare note su ChromeOS, consulta Input compatibilità.
Per registrare un'app come app per prendere note su Android, vedi Creare un'aggiunta di note Google Cloud.
Android 14 (livello API 34), ha introdotto
ACTION_CREATE_NOTE
che consente alla tua app di avviare un'attività per prendere appunti sulla serratura
schermo.
Riconoscimento dell'inchiostro digitale con ML Kit
Con inchiostro digitale ML Kit riconoscimento, la tua app può riconoscere il testo scritto a mano su una piattaforma digitale in centinaia lingue diverse. Puoi anche classificare gli schizzi.
ML Kit offre
Ink.Stroke.Builder
per creare Ink
oggetti che possono essere elaborati dai modelli di machine learning
per convertire la scrittura a mano libera in testo.
Oltre al riconoscimento della scrittura a mano libera, il modello è in grado di riconoscere gesti, come Elimina e cerchia.
Vedi Inchiostro digitale riconoscimento per saperne di più.