ניהול מקטעים

FragmentManager הוא הכיתה שאחראית לביצוע הפעולות על מקטעי האפליקציה, למשל להוסיף, להסיר או להחליף אותן ולהוסיף אותן למקבץ האחורי.

יכול להיות שלעולם לא תהיה לך אפשרות לבצע אינטראקציה ישירה עם FragmentManager אם השתמשת ספריית הניווט ב-Jetpack, מכיוון שהיא פועלת עם 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 המתאים שיש להפנות תלוי במיקום ה-callsite נמצא בהיררכיית המקטעים שבה נמצא מנהל המקטעים ניסית לגשת אליהם.

ברגע שתהיה לך הפניה אל FragmentManager, אפשר להשתמש בו כדי לערוך את המקטעים שמוצגים למשתמש.

שברי צאצא

באופן כללי, האפליקציה שלך מורכבת ממספר אחד או מספר קטן של הפעילויות בפרויקט האפליקציה שלך, כשכל פעילות מייצגת קבוצה של מסכים קשורים. הפעילות עשויה לספק נקודה למקום ניווט ברמה העליונה ומקום להיקף אובייקטים של ViewModel ומצבים אחרים של תצוגה בין מקטעים. מקטע מייצג יעד יחיד ב- אפליקציה.

כשרוצים להציג כמה מקטעים בבת אחת, למשל בתצוגה מפוצלת או במרכז שליטה, ניתן להשתמש במקטעי צאצא שמנוהלים על ידי של מקטע היעד ומנהל המקטעים הצאצא שלו.

עוד תרחישים לדוגמה של מקטעים צאצאים:

  • מסך שקפים, באמצעות ViewPager2 במקטע הורה כדי לנהל סדרה של צאצאים תצוגות מפורטות.
  • ניווט משני בתוך קבוצה של מסכים קשורים.
  • 'ניווט ב-Jetpack' משתמש במקטעים צאצאים כיעדים נפרדים. הפעילות מארחת הורה יחיד 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 כשהמשתמש מנווט חזרה. התצוגה שלו is מושמד במקרה הזה. מידע נוסף זמין במאמר הבא: מחזור החיים של מקטע.

חיפוש מקטע קיים

אפשר לקבל הפניה למקטע הנוכחי בתוך מאגר פריסה באמצעות 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() אפשר להקצות תג באמצעות מאפיין ה-XML מסוג android:tag על מקטעים מוגדרות בפריסה שלך או במהלך 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() methods. השיטות האלה מאפשרות לעבור בין החלקים השונים מקבצים על ידי שמירת ערימה קודמת אחת ושחזור מקבץ אחר.

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 ש שה-framework מספק כדי ליצור מופע חדש של המקטע שלכם. הזה במפעל ברירת מחדל משתמש בהשתקפות כדי למצוא ולהפעיל בנאי ללא ארגומנטים למקטע שלך. המשמעות היא שלא ניתן להשתמש בהגדרות המקוריות שבברירת המחדל כדי לספק יחסי תלות למקטע שלך. המשמעות היא גם שכל שינוי שבו השתמשתם כדי ליצור את המקטע בפעם הראשונה לא נעשה שימוש כברירת מחדל.

כדי לספק יחסי תלות למקטע שלך, או להשתמש בכל סוג של התאמה אישית constructor, במקום זאת ליצור תת-מחלקה FragmentFactory בהתאמה אישית ואז לשנות מברירת המחדל FragmentFactory.instantiate. לאחר מכן אפשר לשנות את הגדרות היצרן של FragmentManager שמוגדרות כברירת מחדל באמצעות במפעל מותאם אישית, שמשמש ליצירה של מקטעים.

נניח שיש לך DessertsFragment שאחראי להצגה וקינוחים פופולריים בעיר שלך, וגם DessertsFragment תלוי במחלקה DessertsRepository שמספקת לו המידע שנחוץ לו כדי להציג למשתמש את ממשק המשתמש הנכון.

אפשר להגדיר שבDessertsFragment יהיה צורך ב-DessertsRepository ב-constructor שלו.

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
}

לקבלת מידע מפורט על תהליך הבדיקה ולעיון בדוגמאות מלאות, ראו בדיקת המקטעים.