Menavigasi antar-fragmen menggunakan animasi

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 masuk dan keluar. Fragmen saat ini memudar saat
            fragmen berikutnya bergeser masuk dari kanan.
Gambar 1. Animasi masuk dan keluar. Fragmen saat ini memudar saat fragmen berikutnya bergeser masuk dari kanan.

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 popEnter dan popExit. Fragmen saat ini menggeser
            layar ke kanan, sementara fragmen sebelumnya makin jelas.
Gambar 2. Animasi popEnter dan popExit. Fragmen saat ini menggeser layar ke kanan, sementara 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.

Transisi fragmen dengan elemen bersama.
Gambar 3. Transisi fragmen dengan elemen bersama.

Berikut cara membuat transisi fragmen dengan elemen bersama pada level yang tinggi:

  1. Menetapkan nama transisi yang unik untuk setiap tampilan elemen bersama.
  2. Menambahkan tampilan elemen bersama dan nama transisi ke FragmentTransaction.
  3. 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.

Contoh

Postingan blog