L'API Fragment offre due modi per utilizzare gli effetti di movimento e le trasformazioni
per connettere visivamente i frammenti durante la navigazione. Uno di questi è il framework dell'animazione, che utilizza sia Animation
sia Animator
. L'altro è il Framework di transizione, che include le transizioni di elementi condivisi.
Puoi specificare effetti personalizzati per l'ingresso e l'uscita dei frammenti e per le transizioni di elementi condivisi tra i frammenti.
- Un effetto Invio determina il modo in cui un frammento entra nello schermo. Ad esempio, puoi creare un effetto per far scorrere il frammento dal bordo dello schermo quando lo accedi.
- Un effetto uscita determina il modo in cui un frammento esce dalla schermata. Ad esempio, puoi creare un effetto per far scomparire il frammento quando lo abbandoni.
- Una transizione di elementi condivisi determina il modo in cui una vista condivisa tra due frammenti si sposta tra loro. Ad esempio, un'immagine visualizzata
in
ImageView
nelle transizioni del frammento A al frammento B quando B diventa visibile.
Impostare animazioni
Innanzitutto, devi creare animazioni per gli effetti di entrata e uscita, che vengono eseguiti quando si passa a un nuovo frammento. Puoi definire le animazioni come risorse di animazione tra due elementi. Queste risorse consentono di definire la modalità di rotazione, ampliamento, dissolvenza e spostamento dei frammenti durante l'animazione. Ad esempio, potresti voler far scomparire il frammento corrente e far scorrere il nuovo frammento 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 eseguiti quando si apre lo stack posteriore, operazione che può essere eseguita quando l'utente tocca il pulsante Su o Indietro. Si tratta delle animazioni popEnter
e popExit
. Ad esempio, quando un utente torna a una schermata precedente, potresti voler spostare il frammento corrente dal bordo destro dello schermo e la dissolvenza del frammento precedente 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 richiamando
FragmentTransaction.setCustomAnimations()
,
trasmettendo le risorse di animazione in base al loro ID risorsa, 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();
Impostare le transizioni
Puoi anche utilizzare le transizioni per definire gli effetti di entrata e uscita. Queste transizioni possono essere definite nei file di risorse XML. Ad esempio, potresti volere che il frammento corrente sbiadisca in uscita e che il nuovo frammento scopra dal 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 di entrata e
setExitTransition()
sul frammento uscita, passando le risorse di transizione con un aumento artificioso
in base al relativo 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)); } }
I frammenti supportano le transizioni ad AndroidX. Sebbene i frammenti supportino anche le transizioni del framework, consigliamo vivamente di utilizzare le transizioni di AndroidX, in quanto sono supportate nei livelli API 14 e successivi e contengono correzioni di bug che non sono presenti nelle versioni precedenti delle transizioni framework.
Utilizzare le transizioni degli elementi condivisi
Parte del framework di transizione, le transizioni degli elementi condivisi determinano in che modo le visualizzazioni corrispondenti si spostano tra due frammenti durante la transizione di un frammento. Ad esempio, potresti volere che un'immagine visualizzata in ImageView
sul frammento A passi al frammento B quando diventa visibile la figura 3.
A livello generale, ecco come eseguire una transizione di frammento con elementi condivisi:
- Assegna un nome univoco a ogni vista degli elementi condivisi.
- Aggiungi visualizzazioni di elementi condivisi e nomi delle transizioni a
FragmentTransaction
. - Imposta un'animazione di transizione degli elementi condivisi.
In primo luogo, devi assegnare un nome di transizione univoco a ogni vista degli elementi condivisi per consentire la mappatura delle viste da un frammento all'altro. Imposta un nome di transizione sugli elementi condivisi in ogni layout di frammento utilizzando ViewCompat.setTransitionName()
, che garantisce la compatibilità per i livelli API 14 e successivi.
Ad esempio, il nome della transizione per un ImageView
nei frammenti A e B può essere assegnato come segue:
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 un frammento all'altro. Aggiungi ogni elemento condiviso a FragmentTransaction
chiamando FragmentTransaction.addSharedElement()
, passando la vista e il nome della transizione della vista corrispondente nel frammento 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 la modalità di transizione degli elementi condivisi da un frammento all'altro, devi impostare una transizione di tipo Invio per il frammento di destinazione. Richiama 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 un elemento Transition
personalizzato, consulta
Creare un'animazione di transizione personalizzata.
changeImageTransform
, utilizzata nell'esempio precedente, è una delle traduzioni predefinite
disponibili che puoi usare. Puoi trovare ulteriori sottoclassi Transition
nel riferimento API per la classe Transition
.
Per impostazione predefinita, la transizione di invio dell'elemento condiviso viene utilizzata anche come transizione di ritorno per gli elementi condivisi. La transizione di ritorno determina in che modo gli elementi condivisi ritornano al frammento precedente quando la transazione del frammento viene scollegata dallo stack posteriore. Se vuoi specificare una transizione di ritorno diversa, puoi farlo utilizzando Fragment.setSharedElementReturnTransition()
nel metodo onCreate()
del frammento.
Compatibilità posteriore predittiva
Puoi utilizzare il dorso predittivo con molte animazioni tra frammenti, ma non tutte. Quando implementi la risposta predittiva, tieni presente i seguenti aspetti:
- Importa
Transitions 1.5.0
o una versione successiva eFragments 1.7.0
o una versione successiva. - Sono supportate le classi e le sottoclassi
Animator
e la libreria AndroidX Transizione. - La libreria
Animation
e il frameworkTransition
non sono supportati. - Le animazioni predittive con frammenti funzionano solo su dispositivi con Android 14 o versioni successive.
setCustomAnimations
,setEnterTransition
,setExitTransition
,setReenterTransition
,setReturnTransition
,setSharedElementEnterTransition
esetSharedElementReturnTransition
sono supportati con il valore indietro predittivo.
Per scoprire di più, consulta Aggiungere il supporto per le animazioni predittive Indietro.
Rinvio delle transizioni
In alcuni casi, potrebbe essere necessario posticipare la transizione del frammento per un breve periodo di tempo. Ad esempio, potrebbe essere necessario attendere che tutte le visualizzazioni nel frammento iniziale siano state misurate e strutturate in modo che Android possa acquisire accuratamente gli stati di inizio e di fine della transizione.
Inoltre, potrebbe essere necessario posticipare la transizione fino al caricamento di alcuni dati necessari. Ad esempio, potresti dover attendere il caricamento delle immagini per gli elementi condivisi. In caso contrario, la transizione potrebbe essere scioccante se il caricamento di un'immagine termina durante o dopo la transizione.
Per posticipare una transizione, devi prima assicurarti che la transazione
di frammento consenta il riordinamento delle modifiche dello stato del frammento. Per consentire il riordinamento delle modifiche dello stato dei frammenti, chiama 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 di entrata:
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(); } }
Una volta caricati i dati e pronto per iniziare la transizione, chiama
Fragment.startPostponedEnterTransition()
.
L'esempio seguente utilizza la libreria Glide per caricare un'immagine in un ImageView
condiviso, posticipando la transizione corrispondente fino al completamento del caricamento dell'immagine.
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 caso di problemi come la connessione a internet lenta di un utente, potrebbe essere necessario che la transizione posticipata inizi dopo un determinato periodo di tempo anziché attendere il caricamento di tutti i dati. In questi casi, puoi
chiamare
Fragment.postponeEnterTransition(long, TimeUnit)
nel metodo onViewCreated()
del frammento di input, passando la durata
e l'unità di tempo. Il posticipo quindi inizia automaticamente una volta trascorso il tempo specificato.
Utilizza le transizioni di elementi condivisi con un RecyclerView
Le transizioni di immissione posticipate non devono iniziare finché non sono state misurate e definite tutte le visualizzazioni nel frammento di entrata. Quando utilizzi una RecyclerView
, devi attendere che i dati vengano caricati e che gli elementi RecyclerView
siano pronti per essere disegnati 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 è impostato un elemento ViewTreeObserver.OnPreDrawListener
sulla parte padre della visualizzazione frammento. Questo serve a garantire che tutte le visualizzazioni del frammento siano state misurate e disposte e siano quindi pronte per essere tracciate prima di iniziare la transizione di inserimento posticipata.
Un altro aspetto da considerare quando si utilizzano le transizioni di elementi condivisi con RecyclerView
è che non è possibile impostare il nome della transizione nel layout XML dell'elemento RecyclerView
perché questo layout è condiviso da un numero arbitrario di elementi. Devi assegnare un nome di transizione univoco affinché
l'animazione di transizione utilizzi la visualizzazione corretta.
Puoi assegnare un nome univoco all'elemento condiviso per la transizione assegnando loro un nome quando ViewHolder
è associato. Ad esempio, se i dati di ogni elemento includono un ID univoco, questo 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.