מעבר לרכיב הניווט

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

המסמך הזה משמש כמדריך לשימוש כללי להעברת אפליקציה קיימת אל להשתמש ברכיב הניווט.

ככלל, ההעברה כוללת את השלבים הבאים:

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

  2. שילוב רכיב הניווט – עבור כל פעילות, בונים תרשים ניווט שמכיל מקטע אחד או יותר שמנוהלים על ידי פעילות. החלפת עסקאות במקטעים בפעולות של רכיב ניווט.

  3. הוספת יעדי פעילות – החלפת startActivity() שיחות ב- פעולות באמצעות יעדי הפעילות.

  4. שילוב פעילויות – שילוב תרשימי ניווט במקרים שבהם פעילויות מרובות חולקות פריסה משותפת.

דרישות מוקדמות

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

העברת לוגיקה של ממשק משתמש ספציפי למסך מפעילויות

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

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

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

חדש: מקטעים

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

בדוגמה הזו, מסכי הרשימה והפרטים הם כרגע פעילויות נפרדות.

יצירת פריסה חדשה לאירוח ממשק המשתמש

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

לתצוגה פשוטה אפשר להשתמש ב-FrameLayout, כמו בדוגמה הבאה דוגמה product_list_host:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/main_content"
   android:layout_height="match_parent"
   android:layout_width="match_parent" />

המאפיין id מתייחס לקטע התוכן שבו אנחנו מוסיפים את המאפיין הזה בהמשך. מקטע.

בשלב הבא, בפונקציה onCreate() של הפעילות, משנים את ההפניה לקובץ הפריסה בפונקציה onCreate של הפעילות שלכם כדי להצביע על קובץ הפריסה החדש הבא:

Kotlin

class ProductListActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Replace setContentView(R.layout.product_list) with the line below
        setContentView(R.layout.product_list_host)
        ...
    }
}

Java

public class ProductListActivity extends AppCompatActivity {
    ...
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // Replace setContentView(R.layout.product_list); with the line below
        setContentView(R.layout.product_list_host);
        ...
    }
}

הפריסה הקיימת (product_list, בדוגמה הזו) משמשת כתצוגה ברמה הבסיסית למקטע שאתם עומדים ליצור.

יצירת מקטע

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

Kotlin

class ProductListFragment : Fragment() {
    // Leave empty for now.
}

Java

public class ProductListFragment extends Fragment {
    // Leave empty for now.
}

העברת לוגיקת הפעילות למקטע

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

לפניכם דוגמה למסך מבוסס-פעילות עם לוגיקת ממשק המשתמש שאנחנו צריכים להעביר:

Kotlin

class ProductListActivity : AppCompatActivity() {

    // Views and/or ViewDataBinding references, Adapters...
    private lateinit var productAdapter: ProductAdapter
    private lateinit var binding: ProductListActivityBinding

    ...

    // ViewModels, System Services, other Dependencies...
    private val viewModel: ProductListViewModel by viewModels()

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity)

        // Post view initialization logic
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }
   ...
}

Java

public class ProductListActivity extends AppCompatActivity {

    // Views and/or ViewDataBinding references, adapters...
    private ProductAdapter productAdapter;
    private ProductListActivityBinding binding;

    ...

    // ViewModels, system services, other dependencies...
    private ProductListViewModel viewModel;

    ...

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // View initialization logic
        DataBindingUtil.setContentView(this, R.layout.product_list_activity);

        // Post view initialization logic
        // Connect adapters
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(v -> { ... });

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts ->
            ...
       );

       // ...and so on
   }

הפעילות שלכם עשויה גם לקבוע מתי ואיך המשתמש ינווט אל במסך הבא, כמו בדוגמה הבאה:

Kotlin

    // Provided to ProductAdapter in ProductListActivity snippet.
    private val productClickCallback = ProductClickCallback { product ->
        show(product)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }

Java

// Provided to ProductAdapter in ProductListActivity snippet.
private ProductClickCallback productClickCallback = this::show;

private void show(Product product) {
    Intent intent = new Intent(this, ProductActivity.class);
    intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
    startActivity(intent);
}

בתוך הקטע, מחלקים את העבודה הזו onCreateView() וגם onViewCreated(), כשרק לוגיקת הניווט נותרה בפעילות:

Kotlin

class ProductListFragment : Fragment() {

    private lateinit var binding: ProductListFragmentBinding
    private val viewModel: ProductListViewModel by viewModels()

     // View initialization logic
    override fun onCreateView(inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list,
                container,
                false
        )
        return binding.root
    }

    // Post view initialization logic
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Connect adapters
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener {...}

        // Subscribe to state
        viewModel.products.observe(this, Observer { myProducts ->
            ...
        })

        // ...and so on
    }

    // Provided to ProductAdapter
    private val productClickCallback = ProductClickCallback { product ->
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            (requireActivity() as ProductListActivity).show(product)
        }
    }
    ...
}

Java

public class ProductListFragment extends Fragment {

    private ProductAdapter productAdapter;
    private ProductListFragmentBinding binding;

    // View initialization logic
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.product_list_fragment,
                container,
                false);
        return binding.getRoot();
    }

    // Post view initialization logic
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {

        // Connect adapters
        binding.productsList.setAdapter(productAdapter);

        // Initialize ViewModels and other dependencies
        ProductListViewModel viewModel = new ViewModelProvider(this)
                .get(ProductListViewModel.class);

        // Initialize view properties, set click listeners, etc.
        binding.productsSearchBtn.setOnClickListener(...)

        // Subscribe to state
        viewModel.getProducts().observe(this, myProducts -> {
            ...
       });

       // ...and so on

    // Provided to ProductAdapter
    private ProductClickCallback productClickCallback = new ProductClickCallback() {
        @Override
        public void onClick(Product product) {
            if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
                ((ProductListActivity) requireActivity()).show(product);
            }
        }
    };
    ...
}

בProductListFragment, שים לב שאין קריאה ל setContentView() כדי להגדיל ולחבר את הפריסה. במקטע, onCreateView() מאתחל את תצוגה בסיסית. onCreateView() לוקחת מופע של LayoutInflater, ואפשר להשתמש בו כדי הגדלת תצוגת הרמה הבסיסית (root) על סמך קובץ משאבים לפריסה. בדוגמה הזאת נעשה שימוש חוזר הפריסה הקיימת של product_list שהייתה בשימוש בפעילות כי לא הייתה כלום צריך לשנות לפריסה עצמה.

אם יש לוגיקה של ממשק משתמש שמופיעה בonStart(), onResume() של הפעילות שלך, פונקציות onPause() או onStop() שלא קשורות לניווט, אפשר להעביר אותן לפונקציות המתאימות של אותו השם במקטע.

אתחול המקטע בפעילות המארח

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

Kotlin

class ProductListActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_list_host)
    }

    fun show(product: Product) {
        val intent = Intent(this, ProductActivity::class.java)
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id)
        startActivity(intent)
    }
}

Java

public class ProductListActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.product_list_host);
    }

    public void show(Product product) {
        Intent intent = new Intent(this, ProductActivity.class);
        intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId());
        startActivity(intent);
    }
}

השלב האחרון הוא ליצור מופע של המקטע ב-onCreate(), רק לאחר ההגדרה של תצוגת התוכן:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.product_list_host)

    if (savedInstanceState == null) {
        val fragment = ProductListFragment()
        supportFragmentManager
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit()
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.product_list_host);

    if (savedInstanceState == null) {
        ProductListFragment fragment = new ProductListFragment();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.main_content, fragment)
                .commit();
    }
}

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

העברת תוספות Intent לקטע

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

בדוגמה הזו, הפונקציה ProductDetailsFragment מקבלת את הארגומנטים ישירות מהתוספות של הכוונה של הפעילות:

Kotlin

...

if (savedInstanceState == null) {
    val fragment = ProductDetailsFragment()

    // Intent extras and Fragment Args are both of type android.os.Bundle.
    fragment.arguments = intent.extras

    supportFragmentManager
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit()
}

...

Java

...

if (savedInstanceState == null) {
    ProductDetailsFragment fragment = new ProductDetailsFragment();

    // Intent extras and fragment Args are both of type android.os.Bundle.
    fragment.setArguments(getIntent().getExtras());

    getSupportFragmentManager()
            .beginTransaction()
            .add(R.id.main_content, fragment)
            .commit();
}

...

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

שילוב רכיב הניווט

אחרי שמשתמשים בארכיטקטורה המבוססת על מקטעים, אפשר להתחיל בשילוב של רכיב הניווט.

קודם כל, מוסיפים לפרויקט את יחסי התלות האחרונים של הניווט. לפי ההוראות נתוני הגרסה של ספריית הניווט

יצירת תרשים ניווט

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

כדי ליצור תרשים ניווט, מתחילים ביצירת תיקיית משאבים חדשה שנקראת navigation כדי להוסיף את התרשים, לוחצים לחיצה ימנית על הספרייה ובוחרים חדש > קובץ משאב לניווט.

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

מוגדר NavHostFragment באמצעות FragmentContainerView ממוקם בתוך פעילות של מארח, כפי שמוצג בדוגמה הבאה:

<androidx.fragment.app.FragmentContainerView
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:id="@+id/main_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

המאפיין app:NavGraph מפנה לתרשים הניווט שמשויך למאפיין הזה מארח ניווט. הגדרת המאפיין הזה מגדילה את תרשים הניווט ומגדירה את התרשים בנכס NavHostFragment. המאפיין app:defaultNavHost מבטיח שה-NavHostFragment מיירט את לחצן 'הקודם' של המערכת.

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

לפריסה פשוטה, אפשר לכלול את FragmentContainerView הזה. כצאצא של השורש ViewGroup:

<FrameLayout
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_height="match_parent"
   android:layout_width="match_parent">

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/main_content"
   android:name="androidx.navigation.fragment.NavHostFragment"
   app:navGraph="@navigation/product_list_graph"
   app:defaultNavHost="true"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

</FrameLayout>

אם תלחצו על הכרטיסייה עיצוב בחלק התחתון, אמור להופיע תרשים דומה להודעה שמוצגת בהמשך. בפינה השמאלית העליונה של התרשים, יעדים, אפשר לראות בטופס הפניה לפעילות של NavHost מתוך layout_name (resource_id).

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

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

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

הסרת עסקאות עם מקטעים

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

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

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

פעילות יחידה בניהול מקטעים מרובים

אם יש לכם פעילות אחת שמנהלת מספר מקטעים, הפעילות שלכם יכול להיראות כך:

Kotlin

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Logic to load the starting destination
        //  when the Activity is first created
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit()
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    fun navigateToProductDetail(productId: String) {
        val fragment = new ProductDetailsFragment()
        val args = Bundle().apply {
            putInt(KEY_PRODUCT_ID, productId)
        }
        fragment.arguments = args

        supportFragmentManager.beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit()
    }
}

Java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Logic to load the starting destination when the activity is first created.
        if (savedInstanceState == null) {
            val fragment = ProductListFragment()
            supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, fragment, ProductListFragment.TAG)
                    .commit();
        }
    }

    // Logic to navigate the user to another destination.
    // This may include logic to initialize and set arguments on the destination
    // fragment or even transition animations between the fragments (not shown here).
    public void navigateToProductDetail(String productId) {
        Fragment fragment = new ProductDetailsFragment();
        Bundle args = new Bundle();
        args.putInt(KEY_PRODUCT_ID, productId);
        fragment.setArguments(args);

        getSupportFragmentManager().beginTransaction()
                .addToBackStack(ProductDetailsFragment.TAG)
                .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG)
                .commit();
    }
}

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

Kotlin

class ProductListFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked
        //  in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
            (requireActivity() as MainActivity).navigateToProductDetail(product.id)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
    // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> (
        ((MainActivity) requireActivity()).navigateToProductDetail(product.getId())
    );
}

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

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_detail" />
    </fragment>
    <fragment
        android:id="@+id/product_detail"
        android:name="com.example.android.persistence.ui.ProductDetailFragment"
        android:label="Product Detail"
        tools:layout="@layout/product_detail">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

לאחר מכן, אפשר לעדכן את הפעילות:

Kotlin

class MainActivity : AppCompatActivity() {

    // No need to load the start destination, handled automatically by the Navigation component
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    // No need to load the start destination, handled automatically by the Navigation component
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

לפעילות כבר לא נדרשת שיטה navigateToProductDetail(). ב אנחנו מעדכנים את הקטע ProductListFragment כך שישתמש ב-NavController כדי לנווט למסך הבא של פרטי המוצר.

העברת ארגומנטים בצורה בטוחה

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

לאחר הפעלת הפלאגין, כל הארגומנטים שהוגדרו ביעד ב- תרשים הניווט גורם ל-framework של רכיב הניווט ליצור מחלקה Arguments שמספקת ארגומנטים בטוחים לסוג ליעד היעד. הגדרת פעולה גורמת לפלאגין ליצור הגדרה של Directions class, שאפשר להשתמש בו כדי להנחות את NavController איך לנווט את המשתמש יעד היעד. כשפעולה מפנה ליעד שדורש ארגומנטים, המחלקה Directions שנוצרה כוללת methods של בנאי מחייבים את הפרמטרים האלה.

בתוך המקטע, משתמשים ב-NavController ובמחלקה Directions שנוצרה כדי מספקים ארגומנטים בטוחים מסוג סוג ליעד היעד, כמו בדוגמה הבאה דוגמה:

Kotlin

class ProductListFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = ProductAdapter(productClickCallback)
        binding.productsList.setAdapter(productAdapter)
    }
    ...

    // The callback makes the call to the NavController to make the transition.
    private val productClickCallback = ProductClickCallback { product ->
        val directions = ProductListDirections.navigateToProductDetail(product.id)
        findNavController().navigate(directions)
    }
}

Java

public class ProductListFragment extends Fragment  {
    ...
    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
        // In this example a callback is passed to respond to an item clicked in a RecyclerView
        productAdapter = new ProductAdapter(productClickCallback);
        binding.productsList.setAdapter(productAdapter);
    }
    ...

    // The callback makes the call to the activity to make the transition.
    private ProductClickCallback productClickCallback = product -> {
        ProductListDirections.ViewProductDetails directions =
                ProductListDirections.navigateToProductDetail(product.getId());
        NavHostFragment.findNavController(this).navigate(directions);
    };
}

ניווט ברמה העליונה

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

הפעילות שתתקבל עשויה להיראות כך:

Kotlin

class MainActivity : AppCompatActivity(),
    NavigationView.OnNavigationItemSelectedListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val toggle = ActionBarDrawerToggle(
                this,
                drawerLayout,
                toolbar,
                R.string.navigation_drawer_open, 
                R.string.navigation_drawer_close
        )
        drawerLayout.addDrawerListener(toggle)
        toggle.syncState()

        navView.setNavigationItemSelectedListener(this)
    }

    override fun onBackPressed() {
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.closeDrawer(GravityCompat.START)
        } else {
            super.onBackPressed()
        }
    }

    override fun onNavigationItemSelected(item: MenuItem): Boolean {
        // Handle navigation view item clicks here.
        when (item.itemId) {
            R.id.home -> {
                val homeFragment = HomeFragment()
                show(homeFragment)
            }
            R.id.gallery -> {
                val galleryFragment = GalleryFragment()
                show(galleryFragment)
            }
            R.id.slide_show -> {
                val slideShowFragment = SlideShowFragment()
                show(slideShowFragment)
            }
            R.id.tools -> {
                val toolsFragment = ToolsFragment()
                show(toolsFragment)
            }
        }
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        drawerLayout.closeDrawer(GravityCompat.START)
        return true
    }
}

private fun show(fragment: Fragment) {

    val drawerLayout = drawer_layout as DrawerLayout
    val fragmentManager = supportFragmentManager

    fragmentManager
            .beginTransaction()
            .replace(R.id.main_content, fragment)
            .commit()

    drawerLayout.closeDrawer(GravityCompat.START)
}

Java

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this,
                drawer,
                toolbar,
                R.string.navigation_drawer_open,
                R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        navigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.home) {
            Fragment homeFragment = new HomeFragment();
            show(homeFragment);
        } else if (id == R.id.gallery) {
            Fragment galleryFragment = new GalleryFragment();
            show(galleryFragment);
        } else if (id == R.id.slide_show) {
            Fragment slideShowFragment = new SlideShowFragment();
            show(slideShowFragment);
        } else if (id == R.id.tools) {
            Fragment toolsFragment = new ToolsFragment();
            show(toolsFragment);
        }

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void show(Fragment fragment) {

        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
        FragmentManager fragmentManager = getSupportFragmentManager();

        fragmentManager
                .beginTransaction()
                .replace(R.id.main_content, fragment)
                .commit();

        drawerLayout.closeDrawer(GravityCompat.START);
    }
}

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

<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/home"
            android:icon="@drawable/ic_menu_camera"
            android:title="@string/menu_home" />
        <item
            android:id="@+id/gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/menu_gallery" />
        <item
            android:id="@+id/slide_show"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/menu_slideshow" />
        <item
            android:id="@+id/tools"
            android:icon="@drawable/ic_menu_manage"
            android:title="@string/menu_tools" />
    </group>
</menu>
<!-- activity_main_graph.xml -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_graph"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.example.HomeFragment"
        android:label="Home"
        tools:layout="@layout/home" />

    <fragment
        android:id="@+id/gallery"
        android:name="com.example.GalleryFragment"
        android:label="Gallery"
        tools:layout="@layout/gallery" />

    <fragment
        android:id="@+id/slide_show"
        android:name="com.example.SlideShowFragment"
        android:label="Slide Show"
        tools:layout="@layout/slide_show" />

    <fragment
        android:id="@+id/tools"
        android:name="com.example.ToolsFragment"
        android:label="Tools"
        tools:layout="@layout/tools" />

</navigation>

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

לאחר מכן אפשר לעדכן את MainActivity כדי לחבר את NavController אל Toolbar וגם NavigationView.

לדוגמה, אפשר לעיין בקטע הקוד הבא:

Kotlin

class MainActivity : AppCompatActivity()  {

    val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) }
    val navController by lazy {
      (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController
    }
    val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)

        // Show and Manage the Drawer and Back Icon
        setupActionBarWithNavController(navController, drawerLayout)

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        navigationView.setupWithNavController(navController)

    }

    override fun onSupportNavigateUp(): Boolean {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation
        return navController.navigateUp(drawerLayout)
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawerLayout;
    private NavController navController;
    private NavigationView navigationView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        drawerLayout = findViewById(R.id.drawer_layout);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        navController = navHostFragment.getNavController();
        navigationView = findViewById(R.id.nav_view);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Show and Manage the Drawer and Back Icon
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);

        // Handle Navigation item clicks
        // This works with no further action on your part if the menu and destination id’s match.
        NavigationUI.setupWithNavController(navigationView, navController);

    }

    @Override
    public boolean onSupportNavigateUp() {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation.
        return NavigationUI.navigateUp(navController, drawerLayout);

    }
}

אפשר להשתמש באותה שיטה גם בניווט המבוסס על BottomNavigationView וניווט מבוסס-תפריט. צפייה עדכון רכיבים בממשק המשתמש ב-NavigationUI תוכלו למצוא דוגמאות נוספות.

הוספת יעדי פעילות

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

קודם כול צריך לזהות מקומות באפליקציה שבהם יש שני תרשימי ניווט נפרדים והם משתמשים ב-startActivity כדי לעבור ביניהם.

בדוגמה הזו יש שני תרשימים (A ו-B) וקריאה startActivity() מ-A ל-B.

Kotlin

fun navigateToProductDetails(productId: String) {
    val intent = Intent(this, ProductDetailsActivity::class.java)
    intent.putExtra(KEY_PRODUCT_ID, productId)
    startActivity(intent)
}

Java

private void navigateToProductDetails(String productId) {
    Intent intent = new Intent(this, ProductDetailsActivity.class);
    intent.putExtra(KEY_PRODUCT_ID, productId);
    startActivity(intent);

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

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

ייצוג ה-XML של גרפים A ו-B עשוי להיראות כך:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List"
        tools:layout="@layout/product_list_fragment">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>

    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details"
        tools:layout="@layout/product_details_host">

        <argument
            android:name="product_id"
            app:argType="integer" />

    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details_fragment">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

תוכלו לנווט לפעילות המארח של תרשים ב' באמצעות אותם מנגנונים משמש לניווט ליעדים עם מקטעים:

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

העברת ארגומנטים של יעד פעילות למקטע יעד התחלה

אם פעילות היעד מקבלת תוספות, כמו בדוגמה הקודמת, יכולים להעביר אותם ישירות ליעד ההתחלה כארגומנטים, אבל צריך להגדיר באופן ידני את תרשים הניווט של המארח, בתוך הפעילות של המארח onCreate(), כדי שתוכלו להעביר את התוספות של Intent כארגומנטים מקטע, כפי שמוצג בהמשך:

Kotlin

class ProductDetailsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.product_details_host)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment
        val navController = navHostFramgent.navController
        navController
                .setGraph(R.navigation.product_detail_graph, intent.extras)
    }

}

Java

public class ProductDetailsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.product_details_host);
        NavHostFragment navHostFragment = (NavHostFragment)
            getSupportFragmentManager().findFragmentById(R.id.main_content);
        NavController navController = navHostFragment.getNavController();
        navController
                .setGraph(R.navigation.product_detail_graph, getIntent().getExtras());
    }

}

אפשר לשלוף את הנתונים מארגומנטים של מקטעים Bundle באמצעות מחלקה של הארגומנטים, כפי שאפשר לראות בדוגמה הבאה:

Kotlin

class ProductDetailsFragment : Fragment() {

    val args by navArgs<ProductDetailsArgs>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val productId = args.productId
        ...
    }
    ...

Java

public class ProductDetailsFragment extends Fragment {

    ProductDetailsArgs args;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        args = ProductDetailsArgs.fromBundle(requireArguments());
    }

    @Override
    public void onViewCreated(@NonNull View view,
            @Nullable Bundle savedInstanceState) {
       int productId = args.getProductId();
       ...
    }
    ...

שילוב פעילויות

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

הדוגמה הבאה משלבת את גרפים א' ו-ב' מהקטע הקודם:

לפני שמשלבים:

<!-- Graph A -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details_activity" />
    </fragment>
    <activity
        android:id="@+id/product_details_activity"
        android:name="com.example.android.persistence.ui.ProductDetailsActivity"
        android:label="Product Details Host"
        tools:layout="@layout/product_details_host">
        <argument android:name="product_id"
            app:argType="integer" />
    </activity>

</navigation>
<!-- Graph B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_detail_graph"
    app:startDestination="@id/product_details">

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>
</navigation>

אחרי שמשלבים:

<!-- Combined Graph A and B -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/product_list_graph"
    app:startDestination="@id/product_list">

    <fragment
        android:id="@+id/product_list"
        android:name="com.example.android.persistence.ui.ProductListFragment"
        android:label="Product List Fragment"
        tools:layout="@layout/product_list">
        <action
            android:id="@+id/navigate_to_product_detail"
            app:destination="@id/product_details" />
    </fragment>

    <fragment
        android:id="@+id/product_details"
        android:name="com.example.android.persistence.ui.ProductDetailsFragment"
        android:label="Product Details"
        tools:layout="@layout/product_details">
        <argument
            android:name="product_id"
            app:argType="integer" />
    </fragment>

</navigation>

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

Kotlin

fun navigateToProductDetails(productId: String) {
    val directions = ProductListDirections.navigateToProductDetail(productId)
    findNavController().navigate(directions)
}

Java

private void navigateToProductDetails(String productId) {
    ProductListDirections.NavigateToProductDetail directions =
            ProductListDirections.navigateToProductDetail(productId);
    Navigation.findNavController(getView()).navigate(directions);

משאבים נוספים

למידע נוסף שקשור לניווט, אפשר לעיין בנושאים הבאים: