مدير جزء

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

قد لا تتفاعل مع FragmentManager مباشرةً إذا كنت تستخدم مكتبة Jetpack Navigation لأنّها تعمل مع FragmentManager نيابةً عنك. في المقابل، إنّ أي تطبيق يستخدم الأجزاء FragmentManager على مستوى ما، لذا من المهم فهم ماهيته وطريقة عمله.

تتناول هذه الصفحة ما يلي:

  • كيفية الوصول إلى FragmentManager.
  • دور FragmentManager في ما يتعلق بالأنشطة والأجزاء
  • كيفية إدارة الحزمة الخلفية باستخدام FragmentManager
  • كيفية توفير البيانات والتبعيات للأجزاء.

الوصول إلى FragmentManager

يمكنك الوصول إلى FragmentManager من خلال نشاط أو من جزء.

تمتلك FragmentActivity وفئاتها الفرعية، مثل AppCompatActivity، إمكانية الوصول إلى FragmentManager من خلال طريقة getSupportFragmentManager().

يمكن أن تستضيف الأجزاء جزءًا واحدًا أو أكثر من الأجزاء الفرعية. داخل الأجزاء، يمكنك الحصول على مرجع إلى FragmentManager الذي يدير عناصر الجزء الثانوي من خلال getChildFragmentManager(). إذا كنت بحاجة إلى الوصول إلى مضيفه FragmentManager، يمكنك استخدام getParentFragmentManager().

إليك بعض الأمثلة للاطّلاع على العلاقات بين الأجزاء ومضيفيها ومثيلات FragmentManager المرتبطة بكل منها.

مثالان على تنسيق واجهة المستخدم يوضِّحان العلاقات بين
            الأجزاء وأنشطة المضيفين
الشكل 1. مثالان على تنسيق واجهة المستخدم يعرضان العلاقات بين الأجزاء وأنشطة المضيف.

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

يستضيف جزء المضيف في المثال 1 جزأين فرعيين يشكلان شاشة منقسمة العرض. يستضيف جزء المضيف في المثال 2 جزءًا ثانويًا واحدًا يشكل جزء العرض من طريقة العرض بالتمرير.

استنادًا إلى هذا الإعداد، يمكنك اعتبار كل مضيف مرتبطًا به على أنّه FragmentManager يدير الأجزاء الفرعية. ويتضح ذلك في الشكل 2 إلى جانب تعيينات المواقع بين supportFragmentManager وparentFragmentManager وchildFragmentManager.

يكون لكل مضيف مدير FragmentManager الخاص به مرتبط به
            لإدارة الأجزاء الفرعية.
الشكل 2. يرتبط كل مضيف بخدمة FragmentManager الخاصة به والتي تدير الأجزاء الفرعية.

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

بعد أن يصبح لديك مرجع إلى FragmentManager، يمكنك استخدامه لمعالجة الأجزاء التي يتم عرضها للمستخدم.

الأجزاء الفرعية

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

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

في ما يلي حالات الاستخدام الأخرى للأجزاء الثانوية:

  • شرائح الشاشة، باستخدام ViewPager2 في جزء رئيسي لإدارة سلسلة من طرق عرض الأجزاء الفرعية.
  • التنقُّل الفرعي ضمن مجموعة من الشاشات ذات الصلة
  • تستخدم Jetpack Navigation الأجزاء الفرعية كوجهات فردية. يستضيف النشاط عنصر NavHostFragment رئيسيًا واحدًا ويملأ مساحته بأجزاء وجهة فرعية مختلفة أثناء تنقّل المستخدمين في تطبيقك.

استخدام FragmentManager

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

عندما ينقر المستخدم على زر الرجوع على جهازه، أو عند الاتصال بـ FragmentManager.popBackStack()، تنبثق معاملة الجزء العلوي من الحزمة. وإذا لم يكن هناك المزيد من المعاملات المتعلّقة بالأجزاء في الحزمة، وإذا لم تكن تستخدم أجزاءً فرعية، سيظهر "الحدث الخلفي" وصولاً إلى النشاط. إذا كنت تستخدم الأجزاء الفرعية، راجِع اعتبارات خاصة للأجزاء الفرعية والشقيقة.

عند طلب الرقم addToBackStack() في إحدى المعاملات، يمكن أن تتضمن المعاملة أي عدد من العمليات، مثل إضافة أجزاء متعددة أو استبدال الأجزاء في حاويات متعددة.

عندما يتم فتح المكدس الخلفي، تنعكس كل هذه العمليات كإجراء ذري واحد. في المقابل، إذا أجريت معاملات إضافية قبل طلب "popBackStack()" وإذا لم تستخدم addToBackStack() لإجراء المعاملة، لا يتم إبطال هذه العمليات. لذلك، تجنَّب خلال سمة FragmentTransaction واحدة تجنُّب التداخُل بين المعاملات التي تؤثر في الحزمة السابقة وغيرها من المعاملات التي لا تؤثر فيها.

إجراء معاملة

لعرض جزء داخل حاوية تنسيق، استخدِم FragmentManager لإنشاء FragmentTransaction. ضمن المعاملة، يمكنك بعد ذلك تنفيذ عملية add() أو replace() على الحاوية.

على سبيل المثال، قد يبدو العنصر FragmentTransaction البسيط على النحو التالي:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack("name") // Name can be null
}

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack("name") // Name can be null
    .commit();

في هذا المثال، يحل ExampleFragment محل الجزء، إن وجد، الموجود حاليًا في حاوية التنسيق المحدّدة من خلال معرّف R.id.fragment_container. من خلال توفير فئة الجزء للطريقة replace()، تتيح FragmentManager معالجة إنشاء مثيل باستخدام FragmentFactory. لمزيد من المعلومات، يُرجى الاطّلاع على قسم إضافة التبعيات إلى الأجزاء.

setReorderingAllowed(true) يحسّن هذا الخيار تغييرات حالة الأجزاء المضمَّنة في المعاملة كي تعمل الصور المتحركة والانتقالات بشكل صحيح. لمزيد من المعلومات حول التنقل مع الرسوم المتحركة والانتقالات، راجع معاملات التجزئة والتنقل بين الأجزاء باستخدام الصور المتحركة.

يؤدي استدعاء addToBackStack() إلى إرسال المعاملة إلى الحزمة الخلفية. ويمكن للمستخدم في وقت لاحق التراجع عن المعاملة وإعادة الجزء السابق من خلال النقر على زر الرجوع. إذا أضفت أو أزلت أجزاء متعددة ضمن معاملة واحدة، فسيتم التراجع عن جميع هذه العمليات عند فتح المكدس الخلفي. يتيح لك الاسم الاختياري المُقدَّم في مكالمة addToBackStack() الرجوع إلى معاملة محددة باستخدام popBackStack().

إذا لم تستدعي addToBackStack() عند إجراء معاملة تؤدي إلى إزالة جزء، سيتم إتلاف الجزء الذي تمت إزالته عند إتمام المعاملة، ولن يتمكّن المستخدم من الرجوع إليها. في حال طلبت الرمز addToBackStack() عند إزالة جزء، سيكون الجزء هو STOPPED فقط وسيتم تغييره لاحقًا إلى RESUMED عند رجوع المستخدم إلى الصفحة. ويتم في هذه الحالة محو طريقة العرض. لمزيد من المعلومات، راجع مراحل نشاط الأجزاء.

البحث عن جزء حالي

يمكنك الحصول على مرجع للجزء الحالي داخل حاوية تنسيق باستخدام findFragmentById(). يمكنك استخدام findFragmentById() للبحث عن جزء إما من خلال رقم التعريف المحدّد عند تضخيمه من ملف XML أو باستخدام رقم تعريف الحاوية عند إضافته في FragmentTransaction. إليك مثال على ذلك:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment =
        (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);

يمكنك بدلاً من ذلك تخصيص علامة فريدة للجزء والحصول على مرجع باستخدام findFragmentByTag(). يمكنك تعيين علامة باستخدام السمة android:tag في XML على الأجزاء التي يتم تحديدها داخل التنسيق أو أثناء عملية add() أو replace() في FragmentTransaction.

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container, "tag")
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentByTag("tag") as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

اعتبارات خاصة للأجزاء التابعة والأجزاء التابعة

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

لتحديد جزء التنقل الأساسي داخل معاملة للجزء، استدع الطريقة setPrimaryNavigationFragment() في المعاملة، مع تمرير مثيل للجزء الذي تتمتع childFragmentManager بعنصر التحكم الأساسي فيه.

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

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

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

دعم عدة حزم خلفية

في بعض الحالات، قد يحتاج تطبيقك إلى إتاحة استخدام حِزم متعددة في الخلفية. وأحد الأمثلة الشائعة هو ما إذا كان تطبيقك يستخدم شريط تنقّل سفلي. تتيح لك السمة FragmentManager إمكانية استخدام عدة حِزم خلفية باستخدام الطريقتَين saveBackStack() وrestoreBackStack(). تتيح لك هذه الطرق التبديل بين المكدسات الخلفية عن طريق حفظ مكدس خلفي واستعادة أخرى مختلفة.

تعمل saveBackStack() بشكل مشابه لاستدعاء popBackStack() مع المعلمة name الاختيارية: يتم عرض المعاملة المحددة وجميع المعاملات التي تليها على المكدس. الفرق هو أنّ saveBackStack() يحفظ حالة جميع الأجزاء في المعاملات المعروضة.

على سبيل المثال، لنفترض أنّك أضفت في السابق جزءًا إلى الحزمة الخلفية من خلال تنفيذ FragmentTransaction باستخدام السمة addToBackStack()، كما هو موضّح في المثال التالي:

Kotlin

supportFragmentManager.commit {
  replace<ExampleFragment>(R.id.fragment_container)
  setReorderingAllowed(true)
  addToBackStack("replacement")
}

Java

supportFragmentManager.beginTransaction()
  .replace(R.id.fragment_container, ExampleFragment.class, null)
  // setReorderingAllowed(true) and the optional string argument for
  // addToBackStack() are both required if you want to use saveBackStack()
  .setReorderingAllowed(true)
  .addToBackStack("replacement")
  .commit();

في هذه الحالة، يمكنك حفظ معاملة التجزئة هذه وحالة ExampleFragment من خلال طلب saveBackStack() على النحو التالي:

Kotlin

supportFragmentManager.saveBackStack("replacement")

Java

supportFragmentManager.saveBackStack("replacement");

يمكنك استدعاء restoreBackStack() باستخدام معلمة الاسم نفسها لاستعادة جميع المعاملات المنبثقة وجميع حالات الأجزاء المحفوظة:

Kotlin

supportFragmentManager.restoreBackStack("replacement")

Java

supportFragmentManager.restoreBackStack("replacement");

توفير التبعيات للأجزاء

عند إضافة جزء، يمكنك إنشاء مثيل للجزء يدويًا وإضافته إلى FragmentTransaction.

Kotlin

fragmentManager.commit {
    // Instantiate a new instance before adding
    val myFragment = ExampleFragment()
    add(R.id.fragment_view_container, myFragment)
    setReorderingAllowed(true)
}

Java

// Instantiate a new instance before adding
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
    .add(R.id.fragment_view_container, myFragment)
    .setReorderingAllowed(true)
    .commit();

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

يستخدم FragmentManager تلقائيًا السمة FragmentFactory التي يوفّرها إطار العمل لإنشاء مثيل جديد للجزء الخاص بك. يستخدم هذا المصنع التلقائي الانعكاس للعثور على دالة إنشاء بدون وسيطة واستدعاءها للجزء الخاص بك. وهذا يعني أنه لا يمكنك استخدام هذا المصنع الافتراضي لتوفير اعتماديات للجزء الخاص بك. ويعني ذلك أيضًا أنّ أي أداة إنشاء مخصّصة استخدمتها لإنشاء الجزء في المرة الأولى لا يتم استخدامها تلقائيًا أثناء الإنشاء.

لتوفير التبعيات للجزء الخاص بك، أو لاستخدام أي أداة إنشاء مخصصة، يمكنك بدلاً من ذلك إنشاء فئة فرعية FragmentFactory مخصصة ثم إلغاء FragmentFactory.instantiate. يمكنك بعد ذلك إلغاء الإعدادات الأصلية التلقائية في FragmentManager من خلال ضبط المصنع المخصّص، والذي يُستخدم بعد ذلك لإنشاء مثيل للأجزاء.

لنفترض أنّ لديك DessertsFragment مسؤول عن عرض الحلويات الرائجة في مسقط رأسك، وأنّ DessertsFragment يعتمد على فئة DessertsRepository توفر له المعلومات التي يحتاجها لعرض واجهة المستخدم الصحيحة للمستخدم.

يمكنك تحديد DessertsFragment بحيث يتطلّب مثيل DessertsRepository في الدالة الإنشائية له.

Kotlin

class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() {
    ...
}

Java

public class DessertsFragment extends Fragment {
    private DessertsRepository dessertsRepository;

    public DessertsFragment(DessertsRepository dessertsRepository) {
        super();
        this.dessertsRepository = dessertsRepository;
    }

    // Getter omitted.

    ...
}

قد يبدو التنفيذ البسيط لـ FragmentFactory مشابهًا لما يلي.

Kotlin

class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
            when (loadFragmentClass(classLoader, className)) {
                DessertsFragment::class.java -> DessertsFragment(repository)
                else -> super.instantiate(classLoader, className)
            }
}

Java

public class MyFragmentFactory extends FragmentFactory {
    private DessertsRepository repository;

    public MyFragmentFactory(DessertsRepository repository) {
        super();
        this.repository = repository;
    }

    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className);
        if (fragmentClass == DessertsFragment.class) {
            return new DessertsFragment(repository);
        } else {
            return super.instantiate(classLoader, className);
        }
    }
}

يؤدي هذا المثال إلى الفئة الفرعية FragmentFactory، ما يؤدي إلى إلغاء الطريقة instantiate() لتوفير منطق إنشاء جزء مخصّص لـ DessertsFragment. ويتم التعامل مع فئات الأجزاء الأخرى من خلال السلوك التلقائي من FragmentFactory إلى super.instantiate().

وبعد ذلك، يمكنك ضبط MyFragmentFactory على أنّه الجهاز للاستخدام في المصنع عند إنشاء أجزاء من تطبيقك من خلال ضبط سمة على FragmentManager. يجب ضبط هذه السمة قبل super.onCreate() الخاص بنشاطك لضمان استخدام MyFragmentFactory عند إعادة إنشاء الأجزاء.

Kotlin

class MealActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance())
        super.onCreate(savedInstanceState)
    }
}

Java

public class MealActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        DessertsRepository repository = DessertsRepository.getInstance();
        getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
        super.onCreate(savedInstanceState);
    }
}

يؤدي ضبط السمة FragmentFactory في النشاط إلى إلغاء إنشاء الأجزاء في التسلسل الهرمي للأجزاء في النشاط. بمعنى آخر، تستخدم السمة childFragmentManager لأي أجزاء فرعية تضيفها إعدادات الأجزاء المخصّصة التي تم ضبطها هنا، ما لم يتم إلغاؤها على المستوى الأدنى.

الاختبار باستخدام Fragmentإِعْدَاد

في بنية نشاط واحدة، اختبِر الأجزاء في عزلة باستخدام الفئة FragmentScenario. بما أنّه لا يمكنك الاعتماد على منطق onCreate المخصّص لنشاطك، يمكنك بدلاً من ذلك تمرير FragmentFactory كوسيطة لاختبار الأجزاء، كما هو موضّح في المثال التالي:

// Inside your test
val dessertRepository = mock(DessertsRepository::class.java)
launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment {
    // Test Fragment logic
}

للحصول على معلومات تفصيلية حول عملية الاختبار هذه وأمثلة كاملة، يمكنك الاطّلاع على اختبار الأجزاء.