Fragment API menyediakan dua cara untuk menggunakan efek gerakan dan transformasi
untuk secara visual menghubungkan fragmen selama navigasi. Salah satunya adalah
Framework Animasi, yang menggunakan
Animation
dan
Animator
. Cara lainnya
adalah Framework Transisi, yang mencakup
transisi elemen bersama.
Anda dapat menentukan efek kustom untuk fragmen masuk dan keluar, serta untuk transisi elemen bersama antar-fragmen.
- Efek masuk menentukan cara fragmen memasuki layar. Misalnya, Anda dapat membuat efek untuk menggeser masuk fragmen dari tepi layar saat menavigasi ke fragmen tersebut.
- Efek keluar menentukan cara fragmen keluar dari layar. Misalnya, Anda dapat membuat efek agar fragmen memudar saat menavigasi menjauhinya.
- Transisi elemen bersama menentukan cara tampilan yang digunakan oleh kedua
fragmen bergerak di antara keduanya. Misalnya, gambar yang ditampilkan
dalam
ImageView
pada fragmen A akan bertransisi menjadi fragmen B setelah B terlihat.
Menyetel animasi
Pertama, Anda harus membuat animasi untuk efek masuk dan keluar, yang dijalankan saat menavigasi ke fragmen baru. Anda dapat menentukan animasi sebagai resource animasi tween. Resource ini memungkinkan Anda menentukan cara fragmen harus memutar, merentangkan, memudar, dan bergerak selama animasi. Misalnya, Anda mungkin ingin fragmen saat ini memudar dan fragmen baru bergeser masuk dari tepi kanan layar, seperti ditunjukkan dalam gambar 1.
Animasi ini dapat ditentukan dalam direktori 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%" />
Anda juga dapat menentukan animasi untuk efek masuk dan keluar yang dijalankan
saat memunculkan data sebelumnya, yang dapat terjadi saat pengguna mengetuk tombol Atas
atau Kembali. Ini disebut animasi popEnter
dan popExit
. Misalnya,
saat pengguna kembali ke layar sebelumnya, Anda mungkin ingin
fragmen saat ini bergeser dari tepi kanan layar dan fragmen sebelumnya
makin jelas.
Animasi ini dapat ditentukan sebagai berikut:
<!-- 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" />
Setelah Anda menentukan animasi, gunakan animasi tersebut dengan memanggil
FragmentTransaction.setCustomAnimations()
,
yang meneruskan resource animasi menurut ID resource-nya, seperti ditunjukkan pada
contoh berikut:
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setCustomAnimations( enter = R.anim.slide_in, exit = R.anim.fade_out, popEnter = R.anim.fade_in, popExit = R.anim.slide_out ) 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();
Menyetel transisi
Anda juga dapat menggunakan transisi untuk menentukan efek masuk dan keluar. Transisi ini dapat ditentukan dalam file resource XML. Misalnya, Anda mungkin ingin fragmen saat ini memudar dan fragmen baru bergeser masuk dari tepi kanan layar. Transisi ini dapat ditentukan sebagai berikut:
<!-- 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" />
Setelah Anda menentukan transisi, terapkan transisi tersebut dengan memanggil
setEnterTransition()
pada fragmen masuk dan
setExitTransition()
pada fragmen keluar, yang meneruskan resource transisi yang di-inflate
menurut ID resource-nya, seperti ditunjukkan pada contoh berikut:
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)); } }
Fragmen mendukung transisi AndroidX. Meskipun fragmen juga mendukung transisi framework, sebaiknya gunakan transisi AndroidX karena transisi tersebut didukung di API level 14 dan yang lebih baru, serta berisi perbaikan bug yang tidak ada di transisi framework versi yang lebih lama.
Menggunakan transisi elemen bersama
Bagian dari Framework Transisi, transisi elemen bersama menentukan cara tampilan terkait berpindah di antara dua fragmen selama transisi fragmen. Misalnya, Anda mungkin ingin
gambar ditampilkan dalam ImageView
pada fragmen A untuk transisi menjadi fragmen B
setelah B terlihat, seperti ditunjukkan pada gambar 3.
Berikut cara membuat transisi fragmen dengan elemen bersama pada level yang tinggi:
- Menetapkan nama transisi yang unik untuk setiap tampilan elemen bersama.
- Menambahkan tampilan elemen bersama dan nama transisi ke
FragmentTransaction
. - Menyetel animasi transisi elemen bersama.
Pertama, Anda harus menetapkan nama transisi yang unik untuk setiap tampilan
elemen bersama untuk memungkinkan tampilan dipetakan dari satu fragmen ke fragmen berikutnya. Tetapkan
nama transisi pada elemen bersama di setiap tata letak fragmen menggunakan
ViewCompat.setTransitionName()
,
yang memberikan kompatibilitas untuk API level 14 dan yang lebih baru.
Contohnya, nama transisi untuk ImageView
dalam fragmen A dan B
dapat ditetapkan sebagai berikut:
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”); } }
Untuk menyertakan elemen bersama dalam transisi fragmen,
FragmentTransaction
Anda harus mengetahui cara setiap tampilan elemen bersama dipetakan dari satu
fragmen ke fragmen berikutnya. Tambahkan setiap elemen bersama
ke FragmentTransaction
dengan memanggil
FragmentTransaction.addSharedElement()
, yang
meneruskan tampilan dan nama transisi tampilan terkait di
fragmen berikutnya, seperti ditunjukkan pada contoh berikut:
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();
Untuk menentukan cara transisi elemen bersama dari satu fragmen ke fragmen
berikutnya, Anda harus menyetel transisi masuk pada fragmen yang akan dinavigasi. Panggil
Fragment.setSharedElementEnterTransition()
dalam metode onCreate()
fragmen, seperti yang ditunjukkan pada contoh berikut:
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); } }
Transisi shared_image
didefinisikan sebagai berikut:
<!-- res/transition/shared_image.xml -->
<transitionSet>
<changeImageTransform />
</transitionSet>
Semua subclass Transition
didukung sebagai transisi elemen bersama. Jika
Anda ingin membuat Transition
kustom, lihat
Membuat animasi transisi kustom.
changeImageTransform
, yang digunakan dalam contoh sebelumnya, adalah salah satu terjemahan bawaan yang tersedia
dan dapat digunakan. Anda dapat menemukan subclass Transition
tambahan dalam referensi API untuk class
Transition
.
Secara default, transisi masuk elemen bersama juga digunakan sebagai transisi
kembali untuk elemen bersama. Transisi kembali menentukan cara
transisi elemen bersama kembali ke fragmen sebelumnya saat transaksi fragmen
dikeluarkan dari data sebelumnya. Jika Anda ingin menentukan transisi kembali
yang berbeda, Anda dapat melakukannya
menggunakan Fragment.setSharedElementReturnTransition()
dalam metode onCreate()
fragmen.
Menunda transisi
Dalam beberapa kasus, Anda mungkin perlu menunda transisi fragmen untuk periode waktu yang singkat. Misalnya, Anda mungkin perlu menunggu hingga semua tampilan dalam fragmen masuk diukur dan ditata sehingga Android dapat mengambil status awal dan akhirnya secara akurat untuk transisi.
Selain itu, transisi Anda mungkin harus ditunda hingga data tertentu yang diperlukan telah dimuat. Misalnya, Anda mungkin harus menunggu hingga gambar dimuat untuk elemen bersama. Jika tidak, transisi mungkin akan terganggu jika gambar selesai dimuat selama atau setelah transisi.
Untuk menunda transisi, Anda harus memastikan terlebih dahulu bahwa transaksi fragmen memungkinkan penyusunan ulang perubahan status fragmen. Untuk mengizinkan penyusunan ulang perubahan status fragmen, panggil FragmentTransaction.setReorderingAllowed()
, seperti yang ditunjukkan dalam contoh berikut:
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();
Untuk menunda transisi masuk, panggil
Fragment.postponeEnterTransition()
dalam metode onViewCreated()
fragmen masuk:
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(); } }
Setelah Anda memuat data dan siap untuk memulai transisi, panggil
Fragment.startPostponedEnterTransition()
.
Contoh berikut menggunakan
library Glide untuk memuat gambar
ke dalam ImageView
bersama, yang menunda transisi terkait hingga pemuatan
gambar selesai.
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) } }
Saat menangani kasus seperti koneksi internet pengguna yang lambat, Anda
mungkin perlu memulai transisi yang ditunda setelah beberapa saat,
bukan menunggu semua data dimuat. Untuk situasi ini, Anda dapat
memanggil
Fragment.postponeEnterTransition(long, TimeUnit)
dalam metode onViewCreated()
fragmen masuk, yang meneruskan durasi
dan satuan waktu. Transisi yang ditunda tersebut otomatis dimulai
setelah waktu yang ditentukan berlalu.
Menggunakan transisi elemen bersama dengan RecyclerView
Transisi masuk yang ditunda tidak boleh dimulai hingga semua tampilan
di fragmen masuk diukur dan ditata. Saat menggunakan
RecyclerView
, Anda harus menunggu
data dimuat dan item RecyclerView
siap digambar
sebelum memulai transisi. Berikut contohnya:
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; } }); } }); } }
Perhatikan bahwa
ViewTreeObserver.OnPreDrawListener
ditetapkan pada induk tampilan fragmen. Hal ini dilakukan untuk memastikan
semua tampilan fragmen telah diukur dan ditata, dan karena itu siap
digambar sebelum memulai transisi masuk yang ditunda.
Hal lain yang perlu dipertimbangkan saat menggunakan transisi elemen bersama dengan
RecyclerView
adalah Anda tidak dapat menetapkan nama transisi
di tata letak XML item RecyclerView
karena jumlah item arbitrer yang berbagi tata letak tersebut. Nama transisi yang unik harus ditetapkan agar animasi
transisi menggunakan tampilan yang tepat.
Anda dapat memberikan nama transisi yang unik untuk elemen bersama setiap item
dengan menetapkannya saat ViewHolder
terikat. Misalnya, jika data untuk
setiap item menyertakan ID unik, data tersebut dapat digunakan sebagai nama transisi,
seperti ditunjukkan pada contoh berikut:
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); ... } }
Referensi lainnya
Untuk mempelajari transisi fragmen lebih lanjut, lihat referensi tambahan berikut.