רכיב הניווט הוא ספרייה שיכולה לנהל ניווט מורכב, אנימציית מעבר, קישורי עומק, וארגומנט מסומן שעבר זמן הידור בין המסכים באפליקציה שלך.
המסמך הזה משמש כמדריך לשימוש כללי להעברת אפליקציה קיימת אל להשתמש ברכיב הניווט.
ככלל, ההעברה כוללת את השלבים הבאים:
העברת לוגיקה של ממשק משתמש ספציפי למסך מפעילויות – העברת ממשק המשתמש של האפליקציה לוגיקה של פעילויות, כדי להבטיח שכל פעילות היא בבעלותה רק של הלוגיקה של רכיבי ממשק משתמש לניווט גלובלי, כמו
Toolbar
, בזמן הענקת גישה של כל מסך למקטע או ליעד מותאם אישית.שילוב רכיב הניווט – עבור כל פעילות, בונים תרשים ניווט שמכיל מקטע אחד או יותר שמנוהלים על ידי פעילות. החלפת עסקאות במקטעים בפעולות של רכיב ניווט.
הוספת יעדי פעילות – החלפת
startActivity()
שיחות ב- פעולות באמצעות יעדי הפעילות.שילוב פעילויות – שילוב תרשימי ניווט במקרים שבהם פעילויות מרובות חולקות פריסה משותפת.
דרישות מוקדמות
המדריך הזה מבוסס על ההנחה שכבר העברת את האפליקציה לשימוש ספריות של 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);
משאבים נוספים
למידע נוסף שקשור לניווט, אפשר לעיין בנושאים הבאים:
- עדכון רכיבים בממשק המשתמש באמצעות NavigationUI - כך מנהלים את הניווט באמצעות סרגל האפליקציות העליון, חלונית ההזזה לניווט וניווט תחתון
- בדיקת ניווט - איך בודקים את תהליכי הניווט באפליקציה