La creazione di un'interfaccia utente è solo una parte della creazione di una vista personalizzata. Devi inoltre fare in modo che la visualizzazione risponda all'input dell'utente in modo da assomigliare all'azione reale che stai imitando.
Fai in modo che gli oggetti nella tua app agiscano come oggetti reali. Ad esempio, non permettere che le immagini nella tua app smettano di esistere e riappariscano altrove perché gli oggetti nel mondo reale non lo fanno. Puoi invece spostare le immagini da una posizione all'altra.
Gli utenti percepiscono un comportamento o una sensazione anche impercettibile nell'interfaccia e reagiscono meglio alle sottigliezze che imitano il mondo reale. Ad esempio, quando gli utenti lanciano un oggetto UI, dai un senso di inerzia all'inizio che ritarda il movimento. Alla fine del movimento, fornisci loro un senso di slancio che porti l'oggetto oltre l'avventura.
Questa pagina mostra come utilizzare le funzionalità del framework Android per aggiungere questi comportamenti reali alla tua visualizzazione personalizzata.
Puoi trovare ulteriori informazioni correlate in Panoramica degli eventi di input e Panoramica dell'animazione della proprietà.
Gestire i gesti di immissione
Come molti altri framework dell'interfaccia utente, Android supporta un modello di eventi di input. Le azioni degli utenti si trasformano in eventi che attivano callback che puoi ignorare per personalizzare il modo in cui l'app risponde all'utente. L'evento di input
più comune nel sistema Android è touch, che attiva
onTouchEvent(android.view.MotionEvent)
.
Esegui l'override di questo metodo per gestire l'evento nel seguente modo:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
Gli eventi touch non sono particolarmente utili di per sé. Le moderne UI touch definiscono le interazioni in termini di gesti quali tocco, tiro, spinta, scorrimento e zoom. Per convertire gli eventi touch non elaborati in gesti, Android
fornisce
GestureDetector
.
Crea un GestureDetector
passando in un'istanza di una classe che implementa GestureDetector.OnGestureListener
.
Se vuoi elaborare solo pochi gesti, puoi estendere GestureDetector.SimpleOnGestureListener
anziché implementare l'interfaccia GestureDetector.OnGestureListener
. Ad esempio, questo codice crea una classe che estende
GestureDetector.SimpleOnGestureListener
e sostituisce
onDown(MotionEvent)
.
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(getContext(), new MyListener());
Indipendentemente dall'utilizzo o meno di GestureDetector.SimpleOnGestureListener
,
implementa sempre un metodo
onDown()
che restituisca true
. Questa operazione è necessaria perché tutti i gesti
iniziano con un messaggio onDown()
. Se restituisci false
da onDown()
, come fa
GestureDetector.SimpleOnGestureListener
, il sistema presuppone che vuoi ignorare il resto del gesto e gli altri metodi di
GestureDetector.OnGestureListener
non vengono chiamati. Restituisci
false
solo da onDown()
se vuoi ignorare un
intero gesto.
Dopo aver implementato GestureDetector.OnGestureListener
e creato
un'istanza di GestureDetector
, puoi utilizzare
GestureDetector
per interpretare gli eventi touch ricevuti in
onTouchEvent()
.
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
Quando passi a onTouchEvent()
un evento tocco che non
riconosce come parte di un gesto, viene restituito false
. Puoi quindi eseguire il tuo codice
di rilevamento dei gesti personalizzato.
Crea un movimento fisicamente plausibile
I gesti sono un modo efficace per controllare i dispositivi touchscreen, ma possono essere controintuitivi e difficili da ricordare, a meno che non producano risultati fisicamente plausibili.
Ad esempio, supponi di voler implementare un gesto di scorrimento orizzontale per far ruotare l'elemento disegnato nella visualizzazione attorno al suo asse verticale. Questo gesto ha senso se l'UI risponde spostandosi rapidamente nella direzione dell'azione, per poi rallentare, come se l'utente spingesse un volano e lo fa girare.
La documentazione su come
animare un gesto di scorrimento fornisce una spiegazione dettagliata su come implementare il tuo comportamento
di scorrimento. Ma simulare la sensazione di un volano non è banale. Per far funzionare correttamente un modello di volano,
sono necessarie molte leggi di fisica e matematica. Fortunatamente, Android offre classi helper per simulare questo e altri comportamenti. La lezione Scroller
è la base per gestire i gesti di scorrimento come il volano.
Per iniziare un fling, chiama
fling()
con la velocità iniziale e i valori x e y minimo e massimo
del fling. Per il valore della velocità, puoi utilizzare il valore calcolato da GestureDetector
.
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
La chiamata a fling()
configura il modello di fisica per il gesto
fling. In seguito, aggiorna Scroller
chiamando
Scroller.computeScrollOffset()
a intervalli regolari. computeScrollOffset()
aggiorna lo stato interno dell'oggetto Scroller
leggendo l'ora attuale e utilizzando il modello fisico per calcolare la posizione x e y in quel momento. Richiama
getCurrX()
e
getCurrY()
per recuperare questi valori.
La maggior parte delle visualizzazioni passa le posizioni x e y
dell'oggetto Scroller
direttamente a
scrollTo()
.
Questo esempio è un po' diverso: utilizza la posizione di scorrimento corrente x
per impostare l'angolo di rotazione della vista.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
La classe Scroller
calcola le posizioni di scorrimento per te, ma non applica automaticamente queste posizioni alla tua vista. Applica nuove coordinate
spesso per rendere l'animazione a scorrimento fluida. Puoi farlo in due modi:
- Forza il nuovo disegno richiamando
postInvalidate()
dopo aver chiamatofling()
. Questa tecnica richiede di calcolare gli offset di scorrimento inonDraw()
e di chiamarepostInvalidate()
ogni volta che l'offset di scorrimento cambia. - Configura un elemento
ValueAnimator
da animare per la durata dell'animazione e aggiungi un listener per elaborare gli aggiornamenti dell'animazione chiamandoaddUpdateListener()
. Questa tecnica consente di animare le proprietà di un elementoView
.
Rendi fluide le tue transizioni
Gli utenti si aspettano che una UI moderna possa passare facilmente da uno stato all'altro: gli elementi dell'interfaccia utente scompaiono in entrata e in uscita invece di apparire e scomparire e i movimenti iniziano e terminano in modo fluido anziché avviarsi e interrompersi bruscamente. Il framework di animazione delle proprietà di Android semplifica le transizioni fluide.
Per utilizzare il sistema di animazione, non modificare direttamente la proprietà ogni volta che una proprietà cambia
l'aspetto della vista. Utilizza invece
ValueAnimator
per apportare la modifica. Nell'esempio seguente,
la modifica del componente secondario selezionato nella vista comporta la rotazione dell'intera vista
visualizzata in modo che il puntatore di selezione sia centrato.
ValueAnimator
modifica la rotazione nell'arco di diverse centinaia
di millisecondi, anziché impostare immediatamente il nuovo valore di rotazione.
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
Se il valore da modificare è una delle proprietà View
di base, eseguire l'animazione è ancora più semplice, perché le viste hanno un elemento ViewPropertyAnimator
integrato ottimizzato per l'animazione simultanea di più proprietà, come nell'esempio seguente:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();