Mostrare o nascondere una visualizzazione utilizzando l'animazione

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

Mentre la tua app è in uso, le nuove informazioni appaiono sullo schermo e quelle vecchie vengono rimosse. Modificare immediatamente ciò che appare sullo schermo può essere fastidioso e gli utenti potrebbero perdersi i nuovi contenuti che appaiono all'improvviso. Le animazioni rallentano le modifiche e attirano l'attenzione dell'utente con il movimento in modo che gli aggiornamenti siano più evidenti.

Esistono tre animazioni comuni che puoi usare per mostrare o nascondere una visualizzazione: rivela animazioni, animazioni a dissolvenza incrociata e animazioni per capovolgere le carte.

Creare un'animazione a dissolvenza incrociata

Un'animazione a dissolvenza incrociata, nota anche come dissolvenza, sbiadisce gradualmente in uscita una View o ViewGroup e contemporaneamente dissolve in un'altra. Questa animazione è utile per le situazioni in cui vuoi cambiare contenuto o visualizzazione nella tua app. L'animazione di dissolvenza incrociata mostrata qui utilizza ViewPropertyAnimator, disponibile per Android 3.1 (livello API 12) e versioni successive.

Ecco un esempio di dissolvenza incrociata da un indicatore di avanzamento a contenuti testuali:

Figura 1. Animazione a dissolvenza incrociata.

Crea le viste

Crea le due viste a cui vuoi applicare la dissolvenza incrociata. Nell'esempio seguente vengono creati un indicatore di avanzamento e una visualizzazione di testo scorrevole:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView style="?android:textAppearanceMedium"
            android:lineSpacingMultiplier="1.2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/lorem_ipsum"
            android:padding="16dp" />

    </ScrollView>

    <ProgressBar android:id="@+id/loading_spinner"
        style="?android:progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>

Configurare l'animazione di dissolvenza incrociata

Per impostare l'animazione a dissolvenza incrociata, procedi nel seguente modo:

  1. Crea variabili membro per le viste per cui vuoi applicare la dissolvenza incrociata. Avrai bisogno di questi riferimenti in un secondo momento, quando modifichi le visualizzazioni durante l'animazione.
  2. Imposta la visibilità della visualizzazione con dissolvenza in entrata su GONE. In questo modo, la visualizzazione non può usare lo spazio di layout e non è disponibile nei calcoli di layout, velocizzando l'elaborazione
  3. Memorizza nella cache la proprietà di sistema config_shortAnimTime in una variabile membro. Questa proprietà definisce una durata "breve" standard per l'animazione. Questa durata è ideale per le animazioni o le animazioni che si verificano di frequente. Sono disponibili anche config_longAnimTime e config_mediumAnimTime.

Di seguito è riportato un esempio in cui viene utilizzato il layout dello snippet di codice precedente come visualizzazione dei contenuti delle attività:

Kotlin

class CrossfadeActivity : Activity() {

    private lateinit var contentView: View
    private lateinit var loadingView: View
    private var shortAnimationDuration: Int = 0
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_crossfade)

        contentView = findViewById(R.id.content)
        loadingView = findViewById(R.id.loading_spinner)

        // Initially hide the content view.
        contentView.visibility = View.GONE

        // Retrieve and cache the system's default "short" animation time.
        shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
    }
    ...
}

Java

public class CrossfadeActivity extends Activity {

    private View contentView;
    private View loadingView;
    private int shortAnimationDuration;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crossfade);

        contentView = findViewById(R.id.content);
        loadingView = findViewById(R.id.loading_spinner);

        // Initially hide the content view.
        contentView.setVisibility(View.GONE);

        // Retrieve and cache the system's default "short" animation time.
        shortAnimationDuration = getResources().getInteger(
                android.R.integer.config_shortAnimTime);
    }
    ...
}

Dissolvenza incrociata delle visualizzazioni

Dopo aver impostato correttamente le viste, applica la dissolvenza incrociata nel seguente modo:

  1. Per la visualizzazione con dissolvenza in entrata, imposta il valore alfa su 0 e la visibilità su VISIBLE dall'impostazione iniziale di GONE. In questo modo la visualizzazione è visibile ma trasparente.
  2. Anima il valore alfa da 0 a 1 per la visualizzazione in dissolvenza in entrata. Per la visualizzazione che scompare, anima il valore alfa da 1 a 0.
  3. Utilizzando onAnimationEnd() in un Animator.AnimatorListener, imposta la visibilità della vista che scompare in GONE. Anche se il valore alfa è 0, l'impostazione della visibilità della vista su GONE impedisce alla visualizzazione di utilizzare lo spazio di layout e lo omette dai calcoli del layout, velocizzando l'elaborazione.

Il seguente metodo mostra un esempio di come eseguire questa operazione:

Kotlin

class CrossfadeActivity : Activity() {

    private lateinit var contentView: View
    private lateinit var loadingView: View
    private var shortAnimationDuration: Int = 0
    ...
    private fun crossfade() {
        contentView.apply {
            // Set the content view to 0% opacity but visible, so that it is
            // visible but fully transparent during the animation.
            alpha = 0f
            visibility = View.VISIBLE

            // Animate the content view to 100% opacity and clear any animation
            // listener set on the view.
            animate()
                    .alpha(1f)
                    .setDuration(shortAnimationDuration.toLong())
                    .setListener(null)
        }
        // Animate the loading view to 0% opacity. After the animation ends,
        // set its visibility to GONE as an optimization step so it doesn't
        // participate in layout passes.
        loadingView.animate()
                .alpha(0f)
                .setDuration(shortAnimationDuration.toLong())
                .setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        loadingView.visibility = View.GONE
                    }
                })
    }
}

Java

public class CrossfadeActivity extends Activity {

    private View contentView;
    private View loadingView;
    private int shortAnimationDuration;
    ...
    private void crossfade() {

        // Set the content view to 0% opacity but visible, so that it is
        // visible but fully transparent during the animation.
        contentView.setAlpha(0f);
        contentView.setVisibility(View.VISIBLE);

        // Animate the content view to 100% opacity and clear any animation
        // listener set on the view.
        contentView.animate()
                .alpha(1f)
                .setDuration(shortAnimationDuration)
                .setListener(null);

        // Animate the loading view to 0% opacity. After the animation ends,
        // set its visibility to GONE as an optimization step so it doesn't
        // participate in layout passes.
        loadingView.animate()
                .alpha(0f)
                .setDuration(shortAnimationDuration)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        loadingView.setVisibility(View.GONE);
                    }
                });
    }
}

Crea un'animazione per girare le schede

La rotazione delle schede passa da una visualizzazione dei contenuti all'altra mostrando un'animazione che emula il capovolgimento di una scheda. L'animazione di rotazione delle schede mostrata qui utilizza FragmentTransaction.

Ecco come si presenta un giro di carte:

Figura 2. Animazione del capovolgimento di carte.

Crea oggetti animato

Per creare l'animazione del capovolgimento delle carte, hai bisogno di quattro animatori. Due animatori sono destinati a quando la parte anteriore della scheda si anima verso l'esterno e a sinistra e quando si anima verso l'interno e il lato sinistro. Gli altri due animatori servono a quando l'animazione del retro della carta da destra e verso destra e quando si anima verso l'esterno e a destra.

card_flip_left_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Before rotating, immediately set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:duration="0" />

    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="-180"
        android:valueTo="0"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="@integer/card_flip_time_full" />

    <!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>

card_flip_left_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="180"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="@integer/card_flip_time_full" />

    <!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>

card_flip_right_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Before rotating, immediately set the alpha to 0. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:duration="0" />

    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="180"
        android:valueTo="0"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="@integer/card_flip_time_full" />

    <!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
    <objectAnimator
        android:valueFrom="0.0"
        android:valueTo="1.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>

card_flip_right_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Rotate. -->
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="-180"
        android:propertyName="rotationY"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:duration="@integer/card_flip_time_full" />

    <!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
    <objectAnimator
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:propertyName="alpha"
        android:startOffset="@integer/card_flip_time_half"
        android:duration="1" />
</set>

Crea le viste

Ogni lato della scheda è un layout separato che può includere tutti i contenuti che desideri, ad esempio due visualizzazioni di testo, due immagini o qualsiasi combinazione di visualizzazioni tra cui passare. Utilizza i due layout nei frammenti animati in seguito. Il seguente layout crea un lato di una scheda, che mostra il testo:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#a6c"
    android:padding="16dp"
    android:gravity="bottom">

    <TextView android:id="@android:id/text1"
        style="?android:textAppearanceLarge"
        android:textStyle="bold"
        android:textColor="#fff"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/card_back_title" />

    <TextView style="?android:textAppearanceSmall"
        android:textAllCaps="true"
        android:textColor="#80ffffff"
        android:textStyle="bold"
        android:lineSpacingMultiplier="1.2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/card_back_description" />

</LinearLayout>

Il layout successivo crea l'altro lato della scheda, che mostra un ImageView:

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/image1"
    android:scaleType="centerCrop"
    android:contentDescription="@string/description_image_1" />

Crea i frammenti

Crea classi di frammenti per il fronte e il retro della scheda. Nelle classi di frammenti, restituisci i layout che hai creato con il metodo onCreateView(). Puoi quindi creare istanze di questo frammento nell'attività principale in cui vuoi mostrare la scheda.

L'esempio seguente mostra le classi di frammenti nidificate all'interno dell'attività padre che le utilizza:

Kotlin

class CardFlipActivity : FragmentActivity() {
    ...
    /**

                    *   A fragment representing the front of the card.
     */
    class CardFrontFragment : Fragment() {

    override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
    ): View = inflater.inflate(R.layout.fragment_card_front, container, false)
    }

    /**
    *   A fragment representing the back of the card.
    */
    class CardBackFragment : Fragment() {

    override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
    ): View = inflater.inflate(R.layout.fragment_card_back, container, false)
    }
}

Java

public class CardFlipActivity extends FragmentActivity {
    ...
    /**
    *   A fragment representing the front of the card.
    */
    public class CardFrontFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_front, container, false);
    }
    }

    /**
    *   A fragment representing the back of the card.
    */
    public class CardBackFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_card_back, container, false);
    }
    }
}

Animazione del capovolgimento delle carte

Visualizza i frammenti all'interno di un'attività padre. Per farlo, crea il layout per la tua attività. L'esempio seguente crea un elemento FrameLayout a cui puoi aggiungere frammenti in fase di runtime:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Nel codice dell'attività, imposta la visualizzazione dei contenuti in base al layout che crei. È buona norma mostrare un frammento predefinito quando viene creata l'attività. La seguente attività di esempio mostra come visualizzare il fronte della carta per impostazione predefinita:

Kotlin

class CardFlipActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_activity_card_flip)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                    .add(R.id.container, CardFrontFragment())
                    .commit()
        }
    }
    ...
}

Java

public class CardFlipActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_activity_card_flip);

        if (savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.container, new CardFrontFragment())
                    .commit();
        }
    }
    ...
}

Con la parte anteriore della scheda visibile, puoi mostrare il retro della scheda con l'animazione di capovolgimento al momento opportuno. Crea un metodo per mostrare l'altro lato della scheda che:

  • Imposta le animazioni personalizzate che hai creato per le transizioni dei frammenti.
  • Sostituisce il frammento visualizzato con un nuovo frammento e anima questo evento con le animazioni personalizzate che hai creato.
  • Aggiunge il frammento visualizzato in precedenza alla pila posteriore dei frammenti; in questo modo, quando l'utente tocca il pulsante Indietro, la scheda viene capovolta.

Kotlin

class CardFlipActivity : FragmentActivity() {
    ...
    private fun flipCard() {
        if (showingBack) {
            supportFragmentManager.popBackStack()
            return
        }

        // Flip to the back.

        showingBack = true

        // Create and commit a new fragment transaction that adds the fragment
        // for the back of the card, uses custom animations, and is part of the
        // fragment manager's back stack.

        supportFragmentManager.beginTransaction()

                // Replace the default fragment animations with animator
                // resources representing rotations when switching to the back
                // of the card, as well as animator resources representing
                // rotations when flipping back to the front, such as when the
                // system Back button is tapped.
                .setCustomAnimations(
                        R.animator.card_flip_right_in,
                        R.animator.card_flip_right_out,
                        R.animator.card_flip_left_in,
                        R.animator.card_flip_left_out
                )

                // Replace any fragments in the container view with a fragment
                // representing the next page, indicated by the just-incremented
                // currentPage variable.
                .replace(R.id.container, CardBackFragment())

                // Add this transaction to the back stack, letting users press
                // the Back button to get to the front of the card.
                .addToBackStack(null)

                // Commit the transaction.
                .commit()
    }
}

Java

public class CardFlipActivity extends FragmentActivity {
    ...
    private void flipCard() {
        if (showingBack) {
            getSupportFragmentManager().popBackStack();
            return;
        }

        // Flip to the back.

        showingBack = true;

        // Create and commit a new fragment transaction that adds the fragment
        // for the back of the card, uses custom animations, and is part of the
        // fragment manager's back stack.

        getSupportFragmentManager()
                .beginTransaction()

                // Replace the default fragment animations with animator
                // resources representing rotations when switching to the back
                // of the card, as well as animator resources representing
                // rotations when flipping back to the front, such as when the
                // system Back button is pressed.
                .setCustomAnimations(
                        R.animator.card_flip_right_in,
                        R.animator.card_flip_right_out,
                        R.animator.card_flip_left_in,
                        R.animator.card_flip_left_out)

                // Replace any fragments in the container view with a fragment
                // representing the next page, indicated by the just-incremented
                // currentPage variable.
                .replace(R.id.container, new CardBackFragment())

                // Add this transaction to the back stack, letting users press
                // Back to get to the front of the card.
                .addToBackStack(null)

                // Commit the transaction.
                .commit();
    }
}

Creare un'animazione di rivelazione circolare

Le animazioni di visualizzazione forniscono agli utenti continuità visiva quando mostri o nascondi un gruppo di elementi dell'interfaccia utente. Il metodo ViewAnimationUtils.createCircularReveal() ti consente di animare un cerchio di ritaglio per mostrare o nascondere una visualizzazione. Questa animazione è fornita nella classe ViewAnimationUtils, disponibile per Android 5.0 (livello API 21) e versioni successive.

Ecco un esempio che mostra come mostrare una vista precedentemente invisibile:

Kotlin

// A previously invisible view.
val myView: View = findViewById(R.id.my_view)

// Check whether the runtime version is at least Android 5.0.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Get the center for the clipping circle.
    val cx = myView.width / 2
    val cy = myView.height / 2

    // Get the final radius for the clipping circle.
    val finalRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()

    // Create the animator for this view. The start radius is 0.
    val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius)
    // Make the view visible and start the animation.
    myView.visibility = View.VISIBLE
    anim.start()
} else {
    // Set the view to invisible without a circular reveal animation below
    // Android 5.0.
    myView.visibility = View.INVISIBLE
}

Java

// A previously invisible view.
View myView = findViewById(R.id.my_view);

// Check whether the runtime version is at least Android 5.0.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Get the center for the clipping circle.
    int cx = myView.getWidth() / 2;
    int cy = myView.getHeight() / 2;

    // Get the final radius for the clipping circle.
    float finalRadius = (float) Math.hypot(cx, cy);

    // Create the animator for this view. The start radius is 0.
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius);

    // Make the view visible and start the animation.
    myView.setVisibility(View.VISIBLE);
    anim.start();
} else {
    // Set the view to invisible without a circular reveal animation below
    // Android 5.0.
    myView.setVisibility(View.INVISIBLE);
}

L'animazione ViewAnimationUtils.createCircularReveal() richiede cinque parametri. Il primo parametro è la visualizzazione che vuoi nascondere o mostrare sullo schermo. I due parametri successivi sono le coordinate X e Y per il centro del cerchio di ritaglio. In genere, si tratta del centro della visualizzazione, ma è possibile utilizzare anche il punto toccato dall'utente per far iniziare l'animazione dal punto in cui viene selezionato. Il quarto parametro è il raggio iniziale del cerchio di ritaglio.

Nell'esempio precedente, il raggio iniziale è impostato su zero, in modo che la vista visualizzata sia nascosta dal cerchio. L'ultimo parametro è il raggio finale del cerchio. Quando mostri una visualizzazione, aumenta il raggio finale rispetto alla visualizzazione in modo che quest'ultima possa essere completamente rivelata prima della fine dell'animazione.

Per nascondere una visualizzazione già visibile:

Kotlin

// A previously visible view.
val myView: View = findViewById(R.id.my_view)

// Check whether the runtime version is at least Android 5.0.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Get the center for the clipping circle.
    val cx = myView.width / 2
    val cy = myView.height / 2

    // Get the initial radius for the clipping circle.
    val initialRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()

    // Create the animation. The final radius is 0.
    val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f)

    // Make the view invisible when the animation is done.
    anim.addListener(object : AnimatorListenerAdapter() {

        override fun onAnimationEnd(animation: Animator) {
            super.onAnimationEnd(animation)
            myView.visibility = View.INVISIBLE
        }
    })

    // Start the animation.
    anim.start()
} else {
    // Set the view to visible without a circular reveal animation below
    // Android 5.0.
    myView.visibility = View.VISIBLE
}

Java

// A previously visible view.
final View myView = findViewById(R.id.my_view);

// Check whether the runtime version is at least Android 5.0.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Get the center for the clipping circle.
    int cx = myView.getWidth() / 2;
    int cy = myView.getHeight() / 2;

    // Get the initial radius for the clipping circle.
    float initialRadius = (float) Math.hypot(cx, cy);

    // Create the animation. The final radius is 0.
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f);

    // Make the view invisible when the animation is done.
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            myView.setVisibility(View.INVISIBLE);
        }
    });

    // Start the animation.
    anim.start();
} else {
    // Set the view to visible without a circular reveal animation below Android
    // 5.0.
    myView.setVisibility(View.VISIBLE);
}

In questo caso, il raggio iniziale del cerchio di ritaglio viene impostato in modo da avere le stesse dimensioni della visualizzazione, in modo che quest'ultima sia visibile prima dell'inizio dell'animazione. Il raggio finale è impostato su zero, in modo che la visualizzazione sia nascosta al termine dell'animazione. Aggiungi un listener all'animazione in modo che la visibilità della visualizzazione possa essere impostata su INVISIBLE al termine dell'animazione.

Risorse aggiuntive