Rendere interattiva una visualizzazione personalizzata

Prova la modalità Scrivi
Jetpack Compose è il toolkit dell'interfaccia utente consigliato per Android. Scopri come utilizzare i layout in Compose.

La creazione di una UI è solo una parte della creazione di una visualizzazione personalizzata. Devi anche fare in modo che la visualizzazione risponda all'input dell'utente in modo da assomigliare molto all'azione nel mondo reale che stai imitando.

Fai in modo che gli oggetti nell'app si comportino come gli oggetti reali. Ad esempio, fai in modo che le immagini nella tua app non scompaiano e riappari altrove, perché questo comportamento non è consentito dagli oggetti nel mondo reale. Sposta invece le immagini da una posizione all'altra.

Gli utenti percepiscono comportamenti o emozioni anche sottile nell'interfaccia e reagiscono meglio alle sfumature che imitano il mondo reale. Ad esempio, quando gli utenti lanciano un oggetto UI, dai loro un senso di inerzia all'inizio che ritarda il movimento. Alla fine del movimento, dai un senso di slancio che porta l'oggetto oltre il movimento.

Questa pagina mostra come utilizzare le funzionalità del framework Android per aggiungere questi comportamenti reali alla visualizzazione personalizzata.

Puoi trovare ulteriori informazioni correlate in Panoramica degli eventi di input e Panoramica animazione della proprietà.

Gestire i gesti di immissione

Come molti altri framework UI, Android supporta un modello di evento di input. Le azioni degli utenti vengono trasformate in eventi che attivano i callback e puoi ignorare i callback per personalizzare il modo in cui la tua app risponde all'utente. L'evento di input più comune nel sistema Android è il touch, che attiva onTouchEvent(android.view.MotionEvent). Sostituisci questo metodo per gestire l'evento come segue:

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 di per sé non sono particolarmente utili. Le moderne UI touch definiscono le interazioni in termini di gesti come tocco, tiro, spinta, scorrimento e zoom. Per convertire gli eventi tocco non elaborati in gesti, Android fornisce GestureDetector.

Costruisci un GestureDetector passando un'istanza di una classe che implementa GestureDetector.OnGestureListener. Se vuoi elaborare solo alcuni gesti, puoi estendere GestureDetector.SimpleOnGestureListener invece di 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 dal fatto che tu utilizzi o meno 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 GestureDetector.SimpleOnGestureListener, il sistema presuppone che tu voglia ignorare il resto del gesto e gli altri metodi di GestureDetector.OnGestureListener non vengono chiamati. Restituisci false da onDown() solo se vuoi ignorare un intero gesto.

Dopo aver implementato GestureDetector.OnGestureListener e creato un'istanza di GestureDetector, puoi utilizzare il tuo 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, restituisce false. Puoi quindi eseguire il tuo codice di rilevamento dei gesti personalizzato.

Crea movimento fisicamente plausibile

I gesti rappresentano uno strumento efficace per controllare i dispositivi touchscreen, ma possono essere controintuitivi e difficili da ricordare a meno che non produca risultati fisicamente plausibili.

Ad esempio, supponi di voler implementare un gesto di scorrimento orizzontale che imposti l'elemento disegnato nella visualizzazione che ruota intorno al suo asse verticale. Questo gesto ha senso se l'interfaccia utente risponde spostandosi rapidamente nella direzione dell'orientamento, quindi rallentando, come se l'utente preme un volano e lo faccia ruotare.

La documentazione su come animare un gesto di scorrimento fornisce una spiegazione dettagliata su come implementare il tuo comportamento di punteggio. Tuttavia, simulare la sensazione di un volano non è banale. Per far funzionare un modello con volano è necessaria molta fisica e matematica. Fortunatamente, Android offre classi helper per simulare questo e altri comportamenti. La classe Scroller è la base per la gestione dei gesti di scorrimento a forma di volano.

Per avviare una corsa, chiama fling() con la velocità iniziale e i valori minimo e massimo x e y della freccia. Per il valore di 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 fisico per il gesto di scorrimento. 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 tempo. Chiama getCurrX() e getCurrY() per recuperare questi valori.

La maggior parte delle viste passa le posizioni x e y dell'oggetto Scroller direttamente a scrollTo(). Questo esempio è leggermente diverso: utilizza la posizione di scorrimento x corrente 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 le applica automaticamente alla tua visualizzazione. Applica spesso nuove coordinate per rendere l'animazione di scorrimento fluida. Puoi farlo in due modi:

  • Forza un nuovo disegno chiamando postInvalidate() dopo aver chiamato fling(). Questa tecnica richiede il calcolo degli offset di scorrimento in onDraw() e la chiamata di postInvalidate() ogni volta che l'offset di scorrimento cambia.
  • Configura un ValueAnimator per l'animazione per la durata dell'animazione e aggiungi un listener per elaborare gli aggiornamenti dell'animazione chiamando addUpdateListener(). Questa tecnica consente di animare le proprietà di un elemento View.

Rendi fluide le transizioni

Gli utenti si aspettano una UI moderna che passi senza intoppi da uno stato all'altro: elementi UI che scorrono all'interno e all'esterno invece di apparire e scomparire, e movimenti che iniziano e terminano in modo fluido invece di avviarsi e arrestarsi bruscamente. Il framework di animazione delle proprietà Android semplifica le transizioni fluide.

Per utilizzare il sistema di animazione, ogni volta che una proprietà cambia ciò che influisce sull'aspetto della vista, non modificare direttamente la proprietà. 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 in un periodo di diverse centinaia di millisecondi, invece di 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 che vuoi modificare è una delle proprietà View di base, l'animazione è ancora più semplice, perché le visualizzazioni hanno un 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();