Animasyonları kullanarak parçalar arasında gezinme

Fragment API, gezinme sırasında parçaları görsel olarak bağlamak için hareket efektlerini ve dönüşümleri kullanmanın iki yolunu sunar. Bunlardan biri, hem Animation hem de Animator kullanan Animasyon Çerçevesi'dir. Diğeri ise paylaşılan öğe geçişlerini içeren Geçiş Çerçevesi'dir.

Parçalara girmek, parçalardan çıkmak ve paylaşılan öğelerin parçalar arasındaki geçişleri için özel efektler belirtebilirsiniz.

  • Enter efekti, bir parçanın ekrana nasıl gireceğini belirler. Örneğin, parçaya gittiğinizde ekranın kenarından içine kaydıracak bir efekt oluşturabilirsiniz.
  • Çıkış efekti, bir parçanın ekrandan nasıl çıkacağını belirler. Örneğin, parçadan çıkarken parçayı şeffaflaştırmak için bir efekt oluşturabilirsiniz.
  • Paylaşılan öğe geçişi, iki parça arasında paylaşılan bir görünümün bunlar arasında nasıl hareket edeceğini belirler. Örneğin, A parçasındaki ImageView içinde gösterilen bir resim, B görünür hale geldiğinde B parçasına geçer.

Animasyonları değiştirme

Öncelikle, yeni bir parçaya giderken çalıştırılan giriş ve çıkış efektleriniz için animasyonlar oluşturmanız gerekir. Animasyonları, animasyon kaynakları ara sayısı olarak tanımlayabilirsiniz. Bu kaynaklar, animasyon sırasında parçaların nasıl döndürüleceğini, uzatılacağını, kaybolacağını ve hareket edeceğini tanımlamanızı sağlar. Örneğin, Şekil 1'de gösterildiği gibi mevcut parçanın şeffaflaşmasını ve yeni parçanın ekranın sağ kenarından kaymasını isteyebilirsiniz.

Animasyonlara giriş ve çıkış. Sonraki parça sağdan kayarak kaybolurken mevcut parça kaybolur.
Şekil 1. Animasyonlara giriş ve çıkış. Sonraki parça sağdan kayarken mevcut parça kaybolur.

Bu animasyonlar res/anim dizininde tanımlanabilir:

<!-- 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%" />

Ayrıca, kullanıcı Yukarı veya Geri düğmesine dokunduğunda gerçekleşebilecek, arka yığın açılırken çalıştırılacak giriş ve çıkış efektleri için animasyonlar da belirleyebilirsiniz. Bunlara, popEnter ve popExit animasyonları denir. Örneğin, bir kullanıcı önceki bir ekrana geri döndüğünde mevcut parçanın ekranın sağ kenarından kaymasını ve önceki parçanın belirmesini isteyebilirsiniz.

popEnter ve popExit animasyonları. Mevcut parça, ekrandan sağa doğru kayarken önceki parça belirir.
Şekil 2. popEnter ve popExit animasyonları. Mevcut parça, ekrandan sağa doğru kayarken önceki parça belirir.

Bu animasyonlar şu şekilde tanımlanabilir:

<!-- 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" />

Animasyonlarınızı tanımladıktan sonra, aşağıdaki örnekte gösterildiği gibi, FragmentTransaction.setCustomAnimations() yöntemini çağırarak animasyon kaynaklarınızı kaynak kimlikleriyle ileterek kullanın:

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();

Geçişleri ayarla

Ayrıca, giriş ve çıkış efektlerini tanımlamak için geçişleri kullanabilirsiniz. Bu geçişler XML kaynak dosyalarında tanımlanabilir. Örneğin, mevcut parçanın solmasını ve yeni parçanın ekranın sağ kenarından kaymasını isteyebilirsiniz. Bu geçişler şu şekilde tanımlanabilir:

<!-- 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" />

Geçişlerinizi tanımladıktan sonra, aşağıdaki örnekte gösterildiği gibi, giriş parçasında setEnterTransition() ve çıkış parçada setExitTransition() yöntemini çağırıp şişirilmiş geçiş kaynaklarınızı kaynak kimlikleriyle geçirerek bunları uygulayın:

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));
    }
}

Parçalar AndroidX geçişlerini destekler. Parçalar çerçeve geçişlerini de desteklese de, API seviyeleri 14 ve sonraki sürümlerde desteklendiğinden ve çerçeve geçişlerinin eski sürümlerinde bulunmayan hata düzeltmeleri içerdiğinden AndroidX geçişlerinin kullanılmasını önemle tavsiye ederiz.

Paylaşılan öğe geçişlerini kullanma

Geçiş Çerçevesi'nin bir parçası olan paylaşılan öğe geçişleri, parça geçişi sırasında karşılık gelen görünümlerin iki parça arasında nasıl hareket edeceğini belirler. Örneğin, Şekil 3'te gösterildiği gibi, A parçasındaki bir ImageView içinde görüntülenen bir görüntünün, B görünür hale geldiğinde B parçasına geçiş yapmasını isteyebilirsiniz.

Paylaşılan öğeye sahip parça geçişi.
Şekil 3. Paylaşılan öğeye sahip parça geçişi.

Genel hatlarıyla, paylaşılan öğelerle parça geçişinin nasıl yapılacağı aşağıda açıklanmıştır:

  1. Paylaşılan öğe görünümlerinin her birine benzersiz bir geçiş adı atayın.
  2. FragmentTransaction'a paylaşılan öğe görünümlerini ve geçiş adlarını ekleyin.
  3. Paylaşılan öğe geçiş animasyonu ayarlayın.

Öncelikle, görünümlerin bir parçadan diğerine eşlenebilmesi için her bir paylaşılan öğe görünümüne benzersiz bir geçiş adı atamanız gerekir. 14 ve sonraki API düzeyleriyle uyumluluk sağlayan ViewCompat.setTransitionName() kullanarak her parça düzeninde paylaşılan öğeler için bir geçiş adı belirleyin. Örnek olarak, A ve B parçalarındaki bir ImageView için geçiş adı aşağıdaki gibi atanabilir:

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”);
    }
}

Paylaşılan öğelerinizi parça geçişine dahil etmek için FragmentTransaction, her bir paylaşılan öğenin görünümünün bir parçadan diğerine nasıl eşlendiğini bilmelidir. Paylaşılan öğelerinizin her birini, aşağıdaki örnekte gösterildiği gibi FragmentTransaction.addSharedElement() yöntemini çağırarak ve sonraki parçada bulunan ilgili görünümün geçiş adını geçirerek görünümden geçirerek FragmentTransaction öğenize ekleyin:

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();

Paylaşılan öğelerinizin bir parçadan diğerine nasıl geçiş yapacağını belirtmek için gidilen parçada enter geçişi ayarlamanız gerekir. Aşağıdaki örnekte gösterildiği gibi parçanın onCreate() yönteminde Fragment.setSharedElementEnterTransition() yöntemini çağırın:

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);
    }
}

shared_image geçişi aşağıdaki şekilde tanımlanır:

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

Tüm Transition alt sınıfları, paylaşılan öğe geçişleri olarak desteklenir. Özel bir Transition oluşturmak istiyorsanız Özel geçiş animasyonu oluşturma konusuna bakın. Önceki örnekte kullanılan changeImageTransform, kullanabileceğiniz önceden oluşturulmuş çevirilerden biridir. Transition sınıfıyla ilgili API referansında ek Transition alt sınıfları bulabilirsiniz.

Varsayılan olarak, paylaşılan öğe giriş geçişi, paylaşılan öğeler için return geçişi olarak da kullanılır. Dönüş geçişi, parça işlemi arka yığından çıkarıldığında paylaşılan öğelerin önceki parçaya nasıl geri döndüğünü belirler. Farklı bir dönüş geçişi belirtmek isterseniz bunu parçanın onCreate() yönteminde Fragment.setSharedElementReturnTransition() kullanarak yapabilirsiniz.

Tahmine dayalı geri uyumluluğu

Hepsi olmasa da çoğu çapraz parça animasyonda tahmine dayalı geri özelliğini kullanabilirsiniz. Tahmine dayalı geri özelliğini uygularken aşağıdaki noktaları göz önünde bulundurun:

  • Transitions 1.5.0 veya sonraki sürümleri ve Fragments 1.7.0 ya da sonraki sürümleri içe aktarın.
  • Animator sınıfı ile alt sınıfları ve AndroidX Transition kitaplığı desteklenir.
  • Animation sınıfı ve çerçeve Transition kitaplığı desteklenmiyor.
  • Tahmine dayalı parça animasyonları yalnızca Android 14 veya sonraki sürümleri çalıştıran cihazlarda çalışır.
  • setCustomAnimations, setEnterTransition, setExitTransition, setReenterTransition, setReturnTransition, setSharedElementEnterTransition ve setSharedElementReturnTransition, tahmine dayalı geri ile desteklenir.

Daha fazla bilgi edinmek için Tahmine dayalı geri animasyonları için destek ekleme bölümüne bakın.

Geçişleri erteleme

Bazı durumlarda, parça geçişinizi kısa bir süreliğine ertelemeniz gerekebilir. Örneğin, Android'in geçiş için başlangıç ve bitiş durumlarını doğru bir şekilde yakalayabilmesi amacıyla, giriş parçasındaki tüm görüntülemelerin ölçülüp yerleştirilmesini beklemeniz gerekebilir.

Ayrıca, gerekli bazı veriler yüklenene kadar geçişinizin ertelenmesi gerekebilir. Örneğin, paylaşılan öğeler için resimler yüklenene kadar beklemeniz gerekebilir. Aksi takdirde, geçiş sırasında veya sonrasında bir resmin yüklenmesi biterse geçişte sorun yaşanabilir.

Bir geçişi ertelemek için öncelikle parça işleminin, parça durumu değişikliklerinin yeniden sıralanmasına izin verdiğinden emin olmanız gerekir. Parça durumu değişikliklerinin yeniden düzenlenmesine izin vermek için aşağıdaki örnekte gösterildiği gibi FragmentTransaction.setReorderingAllowed() çağrısı yapın:

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();

Giriş geçişini ertelemek için parçanın onViewCreated() yönteminde Fragment.postponeEnterTransition() çağrısı yapın:

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();
    }
}

Verileri yükleyip geçişi başlatmaya hazır olduğunuzda Fragment.startPostponedEnterTransition() numaralı telefonu arayın. Aşağıdaki örnekte, paylaşılan bir ImageView'ye resim yüklemek için Glide kitaplığı kullanılır ve ilgili geçiş resim yükleme tamamlanana kadar ertelenir.

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)
    }
}

Kullanıcıların yavaş internet bağlantısı gibi sorunlarla uğraşırken tüm verilerin yüklenmesini beklemek yerine, ertelenmiş bir geçişin belirli bir süre sonra başlaması gerekebilir. Bu durumlarda, bunun yerine süreyi ve zaman birimini kullanarak parçanın onViewCreated() yönteminde Fragment.postponeEnterTransition(long, TimeUnit) çağrısı yapabilirsiniz. Ertelenen süre, belirlenen süre geçtikten sonra otomatik olarak başlar.

RecyclerView ile paylaşılan öğe geçişleri kullanın

Ertelenen giriş geçişleri, giriş parçasındaki tüm görüntülemeler ölçülüp yerleştirilinceye kadar başlamamalıdır. RecyclerView kullanırken geçişi başlatmadan önce tüm verilerin yüklenmesini ve RecyclerView öğenin hazır olmasını beklemeniz gerekir. Aşağıda bununla ilgili bir örnek verilmiştir:

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;
                            }
                    });
                }
        });
    }
}

Parça görünümünün üst öğesinde bir ViewTreeObserver.OnPreDrawListener öğesinin ayarlandığına dikkat edin. Bunun amacı, parçanın tüm görünümlerinin ölçülüp yerleştirildiğinden ve dolayısıyla ertelenen giriş geçişine başlamadan önce çizilmeye hazır olduğundan emin olmaktır.

RecyclerView ile paylaşılan öğe geçişlerini kullanırken dikkat edilmesi gereken bir diğer nokta da RecyclerView öğesinin XML düzeninde geçiş adını ayarlayamayacağınız bir noktadır. Bunun nedeni, bu düzeni rastgele sayıda öğe paylaşmasıdır. Geçiş animasyonunun doğru görünümü kullanabilmesi için benzersiz bir geçiş adı atanmalıdır.

ViewHolder bağlandığında her öğenin paylaşılan öğesine benzersiz bir geçiş adı atayabilirsiniz. Örneğin, her öğenin verileri benzersiz bir kimlik içeriyorsa bu kimlik, aşağıdaki örnekte gösterildiği gibi geçiş adı olarak kullanılabilir:

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);
        ...
    }
}

Ek kaynaklar

Parça geçişleri hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın.

Numuneler

Blog yayınları