التنقّل بين الأجزاء باستخدام الرسوم المتحركة

توفّر واجهة برمجة التطبيقات Fragment API طريقتَين لاستخدام تأثيرات الحركة والإحالات الناجحة. لربط الأجزاء مرئيًا أثناء التنقل. وأحد هذه الأساليب هو وإطار عمل الصور المتحركة، والذي يستخدم كلاً من Animation و Animator تشير رسالة الأشكال البيانية الآخر هو إطار النقل، الذي يتضمن انتقالات العناصر المشتركة.

يمكنك تحديد تأثيرات مخصصة لإدخال الأجزاء والخروج منها، انتقالات العناصر المشتركة بين الأجزاء.

  • يحدِّد تأثير Enter كيفية دخول الجزء إلى الشاشة. على سبيل المثال: يمكنك إنشاء تأثير لتمرير الجزء من حافة الشاشة عند الانتقال إليها.
  • يحدد تأثير الخروج كيفية خروج الجزء من الشاشة. على سبيل المثال: يمكنك إنشاء تأثير لإخفاء الجزء عند الانتقال بعيدًا منه.
  • يحدد انتقال العنصر المشترك كيفية مشاركة طريقة العرض بين يتحرك جزآن بينهما. على سبيل المثال، الصورة المعروضة في ImageView في الكسر A ينتقل إلى الكسر B مرة واحدة B مرئية.

ضبط الصور المتحركة

أولاً، تحتاج إلى إنشاء رسوم متحركة لتأثيرات الدخول والخروج، وهي تشغيلها عند الانتقال إلى جزء جديد. يمكنك تحديد الرسوم المتحركة موارد الصور المتحركة في مرحلة ما قبل المدرسة. وتتيح لك هذه الموارد تحديد كيفية تدوير الأجزاء وتمديدها وتلاشيها والتحرك أثناء الرسوم المتحركة. على سبيل المثال، قد تريد الجزء الحالي ليتلاشى وينزل الجزء الجديد من الحافة اليمنى للشاشة، كما هو موضح في الشكل 1.

إدخال الصور المتحركة والخروج منها يتلاشى الجزء الحالي أثناء
            ينزلق الجزء التالي من اليمين.
الشكل 1. إدخال الصور المتحركة والخروج منها الجزء الحالي يتلاشى مع انحدار الجزء التالي من اليمين.

يمكن تحديد هذه الصور المتحركة في دليل 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%" />

ويمكنك أيضًا تحديد الرسوم المتحركة لتأثيرات الدخول والخروج التي يتم تشغيلها. عند فرقعة المكدس الخلفي، وهو ما قد يحدث عندما ينقر المستخدم فوق المكدس لأعلى أو زر الرجوع وتُعرف هذه الحركة باسم الحركة popEnter وpopExit. بالنسبة على سبيل المثال، عندما يعود مستخدم إلى شاشة سابقة، قد ترغب في الجزء الحالي للخروج من الحافة اليمنى للشاشة والجزء السابق حتى تتلاشى.

الرسوم المتحركة لـpopEnter والنافذة المنبثقة للخروج. يتم إيقاف الجزء الحالي
            الشاشة إلى اليمين بينما يتلاشى الجزء السابق.
الشكل 2. popEnter و popExit صورة متحركة ينزلق الجزء الحالي خارج الشاشة إلى اليسار بينما يتلاشى الجزء السابق.

يمكن تعريف هذه الرسوم المتحركة على النحو التالي:

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

وبمجرد تحديد الصور المتحركة، يمكنك استخدامها عن طريق استدعاء FragmentTransaction.setCustomAnimations()، تمرير موارد الصور المتحركة من خلال رقم تعريف المورد، كما هو موضح في المثال التالي:

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

ضبط الانتقالات

ويمكنك أيضًا استخدام الانتقالات لتحديد تأثيرات الدخول والخروج. هذه يمكن تحديد الانتقالات في ملفات موارد XML. على سبيل المثال، قد تريد أن يتلاشى الجزء الحالي ويختفي الجزء الجديد من الحافة اليمنى من الشاشة. يمكن تحديد هذه الانتقالات على النحو التالي:

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

بعد تحديد الانتقالات، يمكنك تطبيقها عن طريق طلب setEnterTransition() على جزء الإدخال setExitTransition() على الجزء النهائي، وتمرير موارد النقل المتضخمة حسب معرّف المورد، كما هو موضّح في المثال التالي:

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

دعم الأجزاء عمليات انتقال AndroidX: وفي حين أنّ الأجزاء تتوافق أيضًا التحولات في إطار العمل، فإننا نشدد على ننصحك باستخدام عمليات انتقال AndroidX، لأنّها متاحة في المستوى 14 من واجهة برمجة التطبيقات والإصدار الأعلى والتي تحتوي على إصلاحات أخطاء غير موجودة في الإصدارات القديمة من وانتقالات أطر العمل.

استخدام انتقالات العناصر المشتركة

جزء من إطار النقل، تحدد انتقالات العناصر المشتركة كيفية تنقل طرق العرض المقابلة بين جزأين أثناء انتقال الجزء. على سبيل المثال، قد تريد صورة معروضة في ImageView على الجزء "أ" للانتقال إلى الجزء "ب" بمجرد أن تصبح B مرئية، كما هو موضح في الشكل 3.

انتقال جزء مع عنصر مشترك.
الشكل 3. انتقال جزء مع عنصر مشترك.

إليك كيفية إجراء انتقال للأجزاء باستخدام العناصر المشتركة، وذلك على مستوى عالٍ:

  1. عيِّن اسم انتقال فريدًا لكل عرض عنصر مشترك.
  2. أضف طرق عرض العناصر المشتركة وأسماء النقلات إلى FragmentTransaction
  3. ضبط رسم متحرك لعنصر مشترك

أولاً، يجب تعيين اسم انتقال فريد لكل عرض عنصر مشترك للسماح بربط طرق العرض من جزء إلى آخر. إعداد اسم الانتقال على العناصر المشتركة في كل تخطيط جزء باستخدام ViewCompat.setTransitionName()، يوفّر التوافق مع مستويات واجهة برمجة التطبيقات 14 والمستويات الأعلى. على سبيل المثال، اسم النقل لـ ImageView في الجزأين A وB على النحو التالي:

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

لتضمين العناصر المشتركة في نقل الأجزاء، يجب يجب أن يعرف FragmentTransaction كيفية تحديد طريقة عرض كل عنصر مشترك من عنصر إلى الجزء التالي. أضف كل عنصر من العناصر المشتركة إلى FragmentTransaction عن طريق الاتصال FragmentTransaction.addSharedElement(), المرور في العرض واسم الانتقال للعرض المقابل في الجزء التالي، كما هو موضح في المثال التالي:

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

لتحديد كيفية انتقال العناصر المشتركة من جزء إلى آخر، يجب تعيين انتقال enter على الجزء الذي الانتقال إليها. اتصل Fragment.setSharedElementEnterTransition() في طريقة onCreate() للجزء، كما هو موضَّح في المثال التالي:

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 على النحو التالي:

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

يمكن استخدام جميع الفئات الفرعية من Transition كعمليات انتقال عناصر مشتركة. في حال حذف أردت إنشاء Transition مخصّصة، اطّلِع على إنشاء رسم متحرك مخصص: changeImageTransform، المستخدَم في المثال السابق، هو أحد الخيارات المتاحة الترجمات سابقة الإعداد التي يمكنك استخدامها. يمكنك العثور على Transition إضافية. الفئات الفرعية في مرجع واجهة برمجة التطبيقات صف واحد (Transition).

بشكل افتراضي، يتم أيضًا استخدام انتقال إدخال العنصر المشترك return للعناصر المشتركة. تحدّد عملية نقل المرتجعات كيفية تنتقل العناصر المشتركة مرة أخرى إلى الجزء السابق عندما تظهر المعاملة في الحزمة الخلفية. إذا كنت تريد تحديد نوع مختلف من انتقال إرجاع، فيمكنك القيام بذلك باستخدام Fragment.setSharedElementReturnTransition() في طريقة onCreate() للجزء.

التوافق التنبئي مع الرجوع

يمكنك استخدام ميزة الرجوع التنبؤي مع العديد من الرسوم المتحركة ذات الأجزاء المتقاطعة، ولكن ليس كلها. عند تنفيذ التوقُّع التنبئي مرة أخرى، ضع الاعتبارات التالية في الاعتبار:

  • استيراد Transitions 1.5.0 أو إصدار لاحق وFragments 1.7.0 أو إصدار لاحق
  • تتوفر الفئة Animator والفئات الفرعية ومكتبة AndroidX Transition.
  • مكتبة Transition للفئة Animation وإطار العمل غير متاحة.
  • لا تعمل الصور المتحركة للأجزاء القائمة على التوقّعات إلا على الأجهزة التي تعمل بنظام التشغيل Android 14 أو أعلى.
  • setCustomAnimations، setEnterTransition، setExitTransition، setReenterTransition، setReturnTransition، setSharedElementEnterTransition وsetSharedElementReturnTransition مزودًا بظهر تنبؤي.

لمزيد من المعلومات، يُرجى مراجعة إتاحة استخدام الصور المتحركة التي تقترحها الرجوع إلى الصفحة السابقة

تأجيل عمليات النقل

وفي بعض الحالات، قد تحتاج إلى تأجيل انتقال الجزء فترة زمنية قصيرة. على سبيل المثال، قد تحتاج إلى الانتظار حتى يتم عرض جميع في الجزء الذي يتم إدخاله، ويتم قياسه ووضعه حتى يتمكن Android يمكنها تحديد حالتي البداية والنهاية للانتقال بدقة.

بالإضافة إلى ذلك، قد يلزم تأجيل عملية النقل حتى تحميل البيانات الضرورية. على سبيل المثال، قد تحتاج إلى الانتظار حتى تم تحميل من الصور للعناصر المشتركة. وإلا، فقد يكون الانتقال مزعجة في حال انتهاء تحميل صورة أثناء عملية الانتقال أو بعدها.

لتأجيل عملية نقل، يجب أولاً التأكد من أن الجزء تسمح بإعادة ترتيب تغييرات حالة الأجزاء. للسماح بإعادة الترتيب تغييرات حالة تجزئة، طلب FragmentTransaction.setReorderingAllowed()، كما هو موضح في المثال التالي:

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

لتأجيل انتقال الدخول، اتصل Fragment.postponeEnterTransition() في طريقة onViewCreated() للجزء المُدخل:

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

بعد تحميل البيانات والاستعداد لبدء عملية النقل، اتصل Fragment.startPostponedEnterTransition() يستخدم المثال التالي دالة مكتبة بالتمرير لتحميل صورة في ImageView مشترك، مع تأجيل عملية النقل المقابلة حتى اكتمل التحميل.

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

عند التعامل مع حالات مثل بطء اتصال الإنترنت لدى المستخدم، ربما الحاجة إلى بدء الانتقال المؤجل بعد فترة زمنية معينة بدلاً من من انتظار تحميل جميع البيانات. في هذه المواقف، يمكنك الاتصال بدلاً من ذلك Fragment.postponeEnterTransition(long, TimeUnit) في طريقة onViewCreated() للجزء المُدخل، مع مرور المدة والوحدة الزمنية. ثم يبدأ التأجيل تلقائيًا بعد انقضى الوقت المحدد.

استخدام انتقالات العناصر المشتركة مع RecyclerView

يجب ألا تبدأ عمليات انتقال الدخول المؤجَّلة قبل أن تظهر كل المشاهدات في صفحة التسجيل الجزء وقياسه. عند استخدام RecyclerView، عليك الانتظار لأي بيانات لتحميلها ولتكون العناصر الـ RecyclerView جاهزة للرسم قبل بدء عملية النقل. وفي ما يلي مثال لذلك:

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

لاحظ أن ViewTreeObserver.OnPreDrawListener يتم تعيينها على العنصر الرئيسي لعرض التقسيم. يضمن ذلك أن جميع على سبيل المثال، تم قياس مشاهدات الأجزاء وتحديدها، وهي بالتالي جاهزة قبل بدء انتقال الدخول المؤجل.

وهناك نقطة أخرى يجب مراعاتها عند استخدام انتقالات العناصر المشتركة من خلال RecyclerView هو أنه لا يمكنك ضبط اسم انتقال في تنسيق XML لعنصر واحد (RecyclerView) بسبب مشاركة عدد عشوائي من العناصر هذا التخطيط. ويجب تعيين اسم انتقال فريد بحيث الحركة الانتقالية تستخدم العرض الصحيح.

يمكنك إعطاء كل عنصر مشترك اسم انتقال فريد من خلال ويتم تعيينها عند ربط ViewHolder. على سبيل المثال، إذا كانت البيانات الخاصة لكل عنصر معرفًا فريدًا، ويمكن استخدامه كاسم انتقالي، حيث كما هو موضح في المثال التالي:

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

مصادر إضافية

لمزيد من المعلومات حول عمليات نقل الأجزاء، يمكنك الاطّلاع على العناصر الإضافية التالية الموارد.

نماذج

مشاركات المدونة