Spostarsi tra i frammenti utilizzando le animazioni

L'API Fragment offre due modi per usare effetti di movimento e trasformazioni per collegare visivamente i frammenti durante la navigazione. Una di queste è la il framework dell'animazione, che utilizza entrambi Animation e Animator. La altro è il framework per la transizione, che include transizioni agli elementi condivisi.

Puoi specificare effetti personalizzati per l'ingresso e l'uscita da frammenti e per transizioni di elementi condivisi tra i frammenti.

  • Un effetto enter determina il modo in cui un frammento entra nello schermo. Ad esempio: puoi creare un effetto per far scorrere il frammento dal bordo della una schermata quando ci si accede.
  • Un effetto exit determina il modo in cui un frammento esce dalla schermata. Ad esempio: puoi creare un effetto per sfumare il frammento quando esci da lì.
  • Una transizione di elementi condivisi determina il modo in cui una visualizzazione viene condivisa tra si spostano tra loro due frammenti. Ad esempio, un'immagine visualizzata in un ImageView nel frammento A transizioni nel frammento B una volta B diventa visibile.

Impostare animazioni

Innanzitutto, devi creare animazioni per gli effetti di entrata e uscita, ovvero eseguire quando si accede a un nuovo frammento. Puoi definire animazioni come risorse dell'animazione di interpolazione. Queste risorse ti consentono di definire in che modo i frammenti devono ruotare, allungare, dissolversi e si muovono durante l'animazione. Ad esempio, potresti volere che il frammento corrente con una dissolvenza in uscita e il nuovo frammento far scorrere verso l'interno dal bordo destro dello schermo, come mostrato nella Figura 1.

Inserisci e esci dalle animazioni. Il frammento corrente si dissolve mentre
            il frammento successivo entra da destra.
Figura 1. Inserisci e esci dalle animazioni. Il frammento attuale si dissolve in uscita mentre il frammento successivo scorre da destra.

Queste animazioni possono essere definite nella directory res/anim:

<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="1"
    android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="100%"
    android:toXDelta="0%" />

Puoi anche specificare animazioni per gli effetti di entrata e uscita che vengono eseguiti quando apre lo stack posteriore, cosa che può accadere quando l'utente tocca il pulsante Su o Pulsante Indietro. Queste si chiamano animazioni popEnter e popExit. Per Ad esempio, quando un utente torna a una schermata precedente, potresti volere frammento corrente per scorrere fuori dal bordo destro dello schermo e dalla precedente per la dissolvenza in entrata.

le animazioni popEnter e popExit. Il frammento corrente scorre fuori
            lo schermo verso destra, mentre il frammento precedente entra in dissolvenza.
Figura 2. popEnter e popExit animazioni. Il frammento corrente scorre fuori dallo schermo a destra, mentre il frammento precedente entra in dissolvenza.

Queste animazioni possono essere definite come segue:

<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="0%"
    android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="0"
    android:toAlpha="1" />

Dopo aver definito le animazioni, utilizzale chiamando FragmentTransaction.setCustomAnimations(), passando le risorse di animazione per il relativo ID, come mostrato nell'esempio seguente:

Kotlin

supportFragmentManager.commit {
    setCustomAnimations(
        R.anim.slide_in, // enter
        R.anim.fade_out, // exit
        R.anim.fade_in, // popEnter
        R.anim.slide_out // popExit
    )
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(
        R.anim.slide_in,  // enter
        R.anim.fade_out,  // exit
        R.anim.fade_in,   // popEnter
        R.anim.slide_out  // popExit
    )
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Imposta transizioni

Puoi utilizzare le transizioni anche per definire gli effetti di entrata e uscita. Questi queste transizioni possono essere definite nei file di risorse XML. Ad esempio, potresti vuoi che il frammento corrente sbiadisca e il nuovo frammento entri in dissolvenza dalla bordo destro dello schermo. Queste transizioni possono essere definite come segue:

<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:slideEdge="right" />

Dopo aver definito le transizioni, applicale chiamando setEnterTransition() sul frammento che entra setExitTransition() sul frammento uscente, passando le risorse di transizione gonfiate in base all'ID risorsa, come mostrato nell'esempio seguente:

Kotlin

class FragmentA : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        exitTransition = inflater.inflateTransition(R.transition.fade)
    }
}

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        enterTransition = inflater.inflateTransition(R.transition.slide_right)
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setExitTransition(inflater.inflateTransition(R.transition.fade));
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setEnterTransition(inflater.inflateTransition(R.transition.slide_right));
    }
}

Supporto dei frammenti Transizioni AndroidX. Sebbene i frammenti supportano anche transizioni framework, abbiamo consigliamo di utilizzare le transizioni AndroidX, poiché sono supportate nei livelli API 14 e successivi e contengono correzioni di bug non presenti nelle versioni precedenti di transizioni del framework.

Utilizzare le transizioni agli elementi condivisi

Nell'ambito del framework per la transizione, le transizioni degli elementi condivisi determinano lo spostamento tra le visualizzazioni corrispondenti durante una transizione frammentata. Ad esempio, potresti volere un immagine visualizzata in un ImageView sul frammento A alla transizione al frammento B quando B diventa visibile, come mostrato nella figura 3.

Una transizione di frammento con un elemento condiviso.
. Figura 3. Una transizione di frammento con un elemento condiviso.

Ecco come eseguire una transizione di frammento con elementi condivisi:

  1. Assegna un nome univoco alla transizione a ogni visualizzazione degli elementi condivisi.
  2. Aggiungi le viste degli elementi e i nomi delle transizioni condivisi alla FragmentTransaction
  3. Imposta un'animazione di transizione degli elementi condivisi.

Innanzitutto, devi assegnare un nome univoco per la transizione a ogni visualizzazione degli elementi condivisi per consentire la mappatura delle viste da un frammento all'altro. Imposta un nome della transizione sugli elementi condivisi nel layout di ogni frammento utilizzando ViewCompat.setTransitionName(), che offre la compatibilità per i livelli API 14 e successivi. Ad esempio, il nome della transizione di un elemento ImageView nei frammenti A e B può essere assegnato nel seguente modo:

Kotlin

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val itemImageView = view.findViewById<ImageView>(R.id.item_image)
        ViewCompat.setTransitionName(itemImageView, item_image)
    }
}

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val heroImageView = view.findViewById<ImageView>(R.id.hero_image)
        ViewCompat.setTransitionName(heroImageView, hero_image)
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView itemImageView = view.findViewById(R.id.item_image);
        ViewCompat.setTransitionName(itemImageView, item_image);
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView heroImageView = view.findViewById(R.id.hero_image);
        ViewCompat.setTransitionName(heroImageView, hero_image);
    }
}

Per includere gli elementi condivisi nella transizione dei frammenti, FragmentTransaction deve sapere come vengono mappate le visualizzazioni di ogni elemento condiviso da uno dal frammento al successivo. Aggiungi ciascuno dei tuoi elementi condivisi ai tuoi FragmentTransaction chiamando FragmentTransaction.addSharedElement(), passa nella vista e il nome della transizione della vista corrispondente nella successivo, come mostrato nell'esempio seguente:

Kotlin

val fragment = FragmentB()
supportFragmentManager.commit {
    setCustomAnimations(...)
    addSharedElement(itemImageView, hero_image)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(...)
    .addSharedElement(itemImageView, hero_image)
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Per specificare in che modo gli elementi condivisi passano da un frammento all'altro: devi impostare una transizione enter sul frammento raggiunto. Chiama Fragment.setSharedElementEnterTransition() nel metodo onCreate() del frammento, come mostrato nell'esempio seguente:

Kotlin

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedElementEnterTransition = TransitionInflater.from(requireContext())
             .inflateTransition(R.transition.shared_image)
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Transition transition = TransitionInflater.from(requireContext())
            .inflateTransition(R.transition.shared_image);
        setSharedElementEnterTransition(transition);
    }
}

La transizione shared_image è definita come segue:

<!-- res/transition/shared_image.xml -->
<transitionSet>
    <changeImageTransform />
</transitionSet>

Tutte le sottoclassi di Transition sono supportate come transizioni di elementi condivisi. Se vuoi creare una Transition personalizzata, consulta Crea un'animazione della transizione personalizzata. changeImageTransform, utilizzato nell'esempio precedente, è uno dei valori disponibili traduzioni predefinite che puoi usare. Puoi trovare altri Transition nel riferimento API per Transition.

Per impostazione predefinita, la transizione dell'immissione dell'elemento condiviso viene utilizzata anche come Transizione return per gli elementi condivisi. La transizione di ritorno determina il gli elementi condivisi tornano al frammento precedente quando quest'ultimo e la transazione viene estratta dallo stack precedente. Se vuoi specificare un valore di ritorno, puoi farlo utilizzando Fragment.setSharedElementReturnTransition() nel metodo onCreate() del frammento.

Compatibilità predittiva con il retro

Puoi utilizzare il prompt predittivo con molte animazioni con frammenti incrociati, ma non tutte. Quando implementi il modello predittivo, tieni presente quanto segue:

  • Importa Transitions 1.5.0 o una versione successiva e Fragments 1.7.0 o una versione successiva.
  • La classe e le sottoclassi Animator e la libreria AndroidX Transizione sono disponibili supportati.
  • Il corso Animation e la libreria Transition del framework non sono supportati.
  • Le animazioni predittive dei frammenti funzionano solo su dispositivi con Android 14 o in alto.
  • setCustomAnimations, setEnterTransition, setExitTransition setReenterTransition, setReturnTransition, setSharedElementEnterTransition e setSharedElementReturnTransition sono supportato con il pulsante Indietro predittivo.

Per saperne di più, vedi Aggiungi il supporto per le animazioni precedenti predittive.

Posticipo delle transizioni

In alcuni casi, potrebbe essere necessario posticipare la transizione dei frammenti per un breve periodo di tempo. Ad esempio, potresti dover attendere che tutte le visualizzazioni nel frammento che entra sono stati misurati e disposti in modo che Android possono acquisire con precisione i loro stati di inizio e di fine della transizione.

Inoltre, potrebbe essere necessario posticipare la transizione sono stati caricati i dati necessari. Ad esempio, potresti dover attendere sono state caricate immagini per gli elementi condivisi. In caso contrario, la transizione potrebbe potrebbe essere fastidioso se un'immagine termina il caricamento durante o dopo la transizione.

Per posticipare una transizione, devi prima assicurarti che il frammento consente il riordinamento delle modifiche dello stato dei frammenti. Per consentire il riordinamento modifiche allo stato dei frammenti, richiama FragmentTransaction.setReorderingAllowed(), come mostrato nell'esempio seguente:

Kotlin

val fragment = FragmentB()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    setCustomAnimation(...)
    addSharedElement(view, view.transitionName)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setReorderingAllowed(true)
    .setCustomAnimations(...)
    .addSharedElement(view, view.getTransitionName())
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Per posticipare la transizione di invio, chiama Fragment.postponeEnterTransition() nel metodo onViewCreated() del frammento inserito:

Kotlin

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        postponeEnterTransition()
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        postponeEnterTransition();
    }
}

Quando hai caricato i dati e sei pronto per iniziare la transizione, chiama Fragment.startPostponedEnterTransition() L'esempio seguente utilizza il metodo Libreria Glide per caricare un'immagine in un ImageView condiviso, posticipando la transizione corrispondente fino all'immagine caricamento completato.

Kotlin

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        Glide.with(this)
            .load(url)
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }

                override fun onResourceReady(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }
            })
            .into(headerImage)
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        Glide.with(this)
            .load(url)
            .listener(new RequestListener<Drawable>() {
                @Override
                public boolean onLoadFailed(...) {
                    startPostponedEnterTransition();
                    return false;
                }

                @Override
                public boolean onResourceReady(...) {
                    startPostponedEnterTransition();
                    return false;
                }
            })
            .into(headerImage)
    }
}

In casi come la connessione a internet lenta di un utente, potresti la transizione posticipata deve iniziare dopo un certo periodo di tempo, che attendere il caricamento di tutti i dati. In questi casi, puoi chiama invece Fragment.postponeEnterTransition(long, TimeUnit) nel metodo onViewCreated() del frammento entrante, passando la durata e l'unità di tempo. La richiesta posticipata inizia automaticamente una volta sia trascorso il tempo specificato.

Utilizza le transizioni agli elementi condivisi con un RecyclerView

Le transizioni di tipo Invio posticipate non dovrebbero iniziare finché non tutte le visualizzazioni nella di immagini container sono stati misurati e disposti. Se utilizzi un RecyclerView, devi attendere per caricare i dati e per fare in modo che gli elementi RecyclerView siano pronti per essere tracciati prima di iniziare la transizione. Ecco un esempio:

Kotlin

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        postponeEnterTransition()

        // Wait for the data to load
        viewModel.data.observe(viewLifecycleOwner) {
            // Set the data on the RecyclerView adapter
            adapter.setData(it)
            // Start the transition once all views have been
            // measured and laid out
            (view.parent as? ViewGroup)?.doOnPreDraw {
                startPostponedEnterTransition()
            }
        }
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        postponeEnterTransition();

        final ViewGroup parentView = (ViewGroup) view.getParent();
        // Wait for the data to load
        viewModel.getData()
            .observe(getViewLifecycleOwner(), new Observer<List<String>>() {
                @Override
                public void onChanged(List<String> list) {
                    // Set the data on the RecyclerView adapter
                    adapter.setData(it);
                    // Start the transition once all views have been
                    // measured and laid out
                    parentView.getViewTreeObserver()
                        .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw(){
                                parentView.getViewTreeObserver()
                                        .removeOnPreDrawListener(this);
                                startPostponedEnterTransition();
                                return true;
                            }
                    });
                }
        });
    }
}

Nota che un ViewTreeObserver.OnPreDrawListener è impostata nell'elemento padre della vista frammento. Questo serve a garantire che tutti delle viste del frammento sono state misurate e disposte e sono quindi pronte da tracciare prima di iniziare la transizione di invio posticipata.

Un altro aspetto da considerare quando si utilizzano transizioni di elementi condivisi con un RecyclerView è che non puoi impostare il nome della transizione nel Layout XML dell'elemento RecyclerView perché è stato condiviso un numero arbitrario di elementi il layout. È necessario assegnare un nome di transizione univoco in modo che l'animazione della transizione utilizzi la visualizzazione corretta.

Puoi assegnare a ogni elemento condiviso un nome di transizione univoco assegnando loro quando ViewHolder è associato. Ad esempio, se i dati relativi a ogni elemento include un ID univoco, potrebbe essere utilizzato come nome della transizione, come mostrato nell'esempio seguente:

Kotlin

class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val image = itemView.findViewById<ImageView>(R.id.item_image)

    fun bind(id: String) {
        ViewCompat.setTransitionName(image, id)
        ...
    }
}

Java

public class ExampleViewHolder extends RecyclerView.ViewHolder {
    private final ImageView image;

    ExampleViewHolder(View itemView) {
        super(itemView);
        image = itemView.findViewById(R.id.item_image);
    }

    public void bind(String id) {
        ViewCompat.setTransitionName(image, id);
        ...
    }
}

Risorse aggiuntive

Per saperne di più sulle transizioni dei frammenti, consulta le seguenti risorse aggiuntive Google Cloud.

Campioni

Blog post