FragmentManager
هي الفئة المسؤولة عن تنفيذ الإجراءات على أجزاء التطبيق، مثل إضافتها أو إزالتها أو استبدالها وإضافتها إلى الحزمة الخلفية.
قد لا تتفاعل مع FragmentManager
مباشرةً إذا كنت تستخدم مكتبة Jetpack Navigation لأنّها تعمل مع FragmentManager
نيابةً عنك. في المقابل، إنّ أي تطبيق يستخدم الأجزاء FragmentManager
على مستوى ما، لذا من المهم فهم ماهيته وطريقة عمله.
تتناول هذه الصفحة ما يلي:
- كيفية الوصول إلى
FragmentManager
. - دور
FragmentManager
في ما يتعلق بالأنشطة والأجزاء - كيفية إدارة الحزمة الخلفية باستخدام
FragmentManager
- كيفية توفير البيانات والتبعيات للأجزاء.
الوصول إلى FragmentManager
يمكنك الوصول إلى FragmentManager
من خلال نشاط أو من جزء.
تمتلك FragmentActivity
وفئاتها الفرعية، مثل
AppCompatActivity
،
إمكانية الوصول إلى FragmentManager
من خلال
طريقة
getSupportFragmentManager()
.
يمكن أن تستضيف الأجزاء جزءًا واحدًا أو أكثر من الأجزاء الفرعية. داخل
الأجزاء، يمكنك الحصول على مرجع إلى FragmentManager
الذي يدير
عناصر الجزء الثانوي من خلال
getChildFragmentManager()
.
إذا كنت بحاجة إلى الوصول إلى مضيفه FragmentManager
، يمكنك استخدام
getParentFragmentManager()
.
إليك بعض الأمثلة للاطّلاع على العلاقات بين الأجزاء ومضيفيها ومثيلات FragmentManager
المرتبطة بكل منها.
يوضح الشكل 1 مثالين، لكل منهما مضيف نشاط واحد. يعرض نشاط المضيف في كلا المثالَين التنقّل على المستوى الأعلى للمستخدم بصفته
BottomNavigationView
مسؤولاً عن استبدال جزء المضيف بشاشات
مختلفة في التطبيق. ويتم وضع كل شاشة كجزء منفصل.
يستضيف جزء المضيف في المثال 1 جزأين فرعيين يشكلان شاشة منقسمة العرض. يستضيف جزء المضيف في المثال 2 جزءًا ثانويًا واحدًا يشكل جزء العرض من طريقة العرض بالتمرير.
استنادًا إلى هذا الإعداد، يمكنك اعتبار كل مضيف مرتبطًا به على أنّه FragmentManager
يدير الأجزاء الفرعية. ويتضح ذلك في الشكل 2 إلى جانب تعيينات المواقع بين supportFragmentManager
وparentFragmentManager
وchildFragmentManager
.
وتعتمد السمة 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 }
للحصول على معلومات تفصيلية حول عملية الاختبار هذه وأمثلة كاملة، يمكنك الاطّلاع على اختبار الأجزاء.