Rendere interattiva una visualizzazione personalizzata

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

Il disegno di un'interfaccia utente è 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 il più possibile all'azione reale che stai imitando.

Fai in modo che gli oggetti nella tua app si comportino come gli oggetti reali. Ad esempio, non consentire che le immagini nella tua app scompaiano e ricompaiano altrove, perché gli oggetti nel mondo reale non lo fanno. Sposta invece le immagini da un luogo all'altro.

Gli utenti percepiscono anche il comportamento o l'aspetto più sottile di un'interfaccia e reagiscono meglio alle sottigliezze 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 all'oggetto un senso di inerzia che lo porti oltre il lancio.

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 delle proprietà.

Gestire i gesti di input

Come molti altri framework UI, Android supporta un modello di eventi di input. Le azioni dell'utente si trasformano in eventi che attivano i callback e puoi eseguire l'override dei callback per personalizzare il modo in cui la tua app risponde all'utente. L'evento di input più comune nel sistema Android è il tocco, 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 di per sé non sono particolarmente utili. Le moderne UI touch definiscono le interazioni in termini di gesti come toccare, tirare, spingere, scorrere rapidamente e zoomare. 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 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 di GestureDetector.SimpleOnGestureListener, implementa sempre un metodo onDown() che restituisce true. Questo è necessario 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 solo false da onDown() se vuoi ignorare un'intera gesture.

Dopo aver implementato GestureDetector.OnGestureListener e creato un'istanza di GestureDetector, puoi utilizzare GestureDetector per interpretare gli eventi tocco che ricevi 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 onTouchEvent() un evento tocco che non riconosce come parte di un gesto, restituisce false. Puoi quindi eseguire il tuo codice personalizzato di rilevamento dei gesti.

Creare 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, supponiamo di voler implementare un gesto di scorrimento orizzontale che imposti la rotazione dell'elemento disegnato nella visualizzazione attorno al suo asse verticale. Questo gesto ha senso se l'interfaccia utente risponde muovendosi rapidamente nella direzione dello scorrimento veloce, per poi rallentare, come se l'utente spingesse un volano e lo facesse 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 conoscenze di fisica e matematica. Fortunatamente, Android fornisce classi helper per simulare questo e altri comportamenti. La classe Scroller è la base per la gestione dei movimenti di scorrimento in stile volano.

Per avviare uno scorrimento, chiama fling() con la velocità iniziale e i valori minimo e massimo di x e y dello scorrimento. 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 fisico per il gesto di scorrimento rapido. Dopodiché, aggiorna Scroller chiamando Scroller.computeScrollOffset() a intervalli regolari. computeScrollOffset() aggiorna lo stato interno dell'oggetto Scroller leggendo l'ora corrente e utilizzando il modello fisico per calcolare la posizione x e y in quel momento. Chiama 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 è leggermente diverso: utilizza la posizione x di scorrimento corrente per impostare l'angolo di rotazione della visualizzazione.

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 visualizzazione. Applica nuove coordinate con una frequenza sufficiente a rendere fluida l'animazione di scorrimento. Puoi farlo in due modi:

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

Rendi fluide le transizioni

Gli utenti si aspettano che una UI moderna passi da uno stato all'altro in modo fluido: gli elementi della UI appaiono e scompaiono gradualmente, mentre i movimenti iniziano e terminano in modo fluido, anziché bruscamente. Il framework di animazione delle proprietà di Android semplifica le transizioni fluide.

Per utilizzare il sistema di animazione, ogni volta che una proprietà modifica l'aspetto della visualizzazione, non modificare direttamente la proprietà. Utilizza invece ValueAnimator per apportare la modifica. Nell'esempio seguente, la modifica del componente secondario selezionato nella visualizzazione fa ruotare l'intera visualizzazione in modo che il puntatore di selezione sia centrato. ValueAnimator modifica la rotazione in un periodo 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 che vuoi modificare è una delle proprietà di base View, 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();