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.
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.
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.
Ecco come eseguire una transizione di frammento con elementi condivisi:
- Assegna un nome univoco alla transizione a ogni visualizzazione degli elementi condivisi.
- Aggiungi le viste degli elementi e i nomi delle transizioni condivisi alla
FragmentTransaction
- 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 eFragments 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 libreriaTransition
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
esetSharedElementReturnTransition
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
- Transizioni continue degli elementi condivisi: da RecyclerView a ViewPager
- Transizioni dei frammenti