Rendere interattiva una visualizzazione personalizzata

Prova il metodo Scrivi
Jetpack Compose è il toolkit consigliato per la UI per Android. Scopri come utilizzare i layout in Compose.

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 chiamato fling(). Questa tecnica richiede di calcolare gli offset di scorrimento in onDraw() e di chiamare postInvalidate() 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 chiamando addUpdateListener(). Questa tecnica consente di animare le proprietà di un elemento View.

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();