مؤلفه ناوبری کتابخانه ای است که می تواند ناوبری پیچیده، انیمیشن انتقال، پیوند عمیق و آرگومان بررسی شده در زمان کامپایل را که بین صفحه های برنامه شما ارسال می شود، مدیریت کند.
این سند به عنوان یک راهنمای همه منظوره برای انتقال یک برنامه موجود برای استفاده از مؤلفه ناوبری عمل می کند.
در سطح بالا، مهاجرت شامل مراحل زیر است:
انتقال منطق رابط کاربری خاص صفحه به خارج از فعالیتها - منطق رابط کاربری برنامه خود را از فعالیتها خارج کنید، اطمینان حاصل کنید که هر فعالیت فقط دارای منطق مؤلفههای رابط کاربری ناوبری جهانی، مانند
Toolbar
است، در حالی که اجرای هر صفحه را به یک قطعه یا سفارشی واگذار میکند. مقصدکامپوننت ناوبری را یکپارچه کنید - برای هر فعالیت، یک نمودار ناوبری بسازید که شامل یک یا چند بخش مدیریت شده توسط آن فعالیت است. تراکنش های قطعه را با عملیات جزء ناوبری جایگزین کنید.
افزودن مقصدهای فعالیت - فراخوانی های
startActivity()
را با اقداماتی که از مقصدهای فعالیت استفاده می کنند جایگزین کنید.ترکیب فعالیت ها - نمودارهای ناوبری را در مواردی که چندین فعالیت یک طرح مشترک دارند ترکیب کنید.
پیش نیازها
این راهنما فرض می کند که شما قبلاً برنامه خود را برای استفاده از کتابخانه های AndroidX منتقل کرده اید. اگر این کار را نکرده اید، قبل از ادامه پروژه خود را برای استفاده از AndroidX انتقال دهید .
منطق UI مخصوص صفحه نمایش را از فعالیت ها خارج کنید
Activities اجزایی در سطح سیستم هستند که تعامل گرافیکی بین برنامه شما و Android را تسهیل می کنند. فعالیتها در مانیفست برنامه شما ثبت میشوند تا Android بداند کدام فعالیتها برای راهاندازی در دسترس هستند. کلاس اکتیویتی به برنامه شما امکان می دهد به تغییرات اندروید نیز واکنش نشان دهد، مانند زمانی که رابط کاربری برنامه شما در حال ورود یا خروج از پیش زمینه، چرخش و غیره است. این فعالیت همچنین میتواند به عنوان مکانی برای اشتراکگذاری حالت بین صفحهها عمل کند.
در زمینه برنامه شما، فعالیتها باید به عنوان میزبانی برای پیمایش عمل کنند و منطق و دانش نحوه انتقال بین صفحهها، انتقال داده و غیره را در خود داشته باشند. با این حال، مدیریت جزئیات UI شما بهتر است به بخش کوچکتر و قابل استفاده مجدد از UI خود واگذار شود. پیاده سازی توصیه شده برای این الگو قطعات است. برای کسب اطلاعات بیشتر در مورد مزایای استفاده از قطعات، به Single Activity: Why, When, and How مراجعه کنید. ناوبری از قطعات از طریق وابستگی ناوبری-قطعه پشتیبانی می کند. ناوبری همچنین از انواع مقصد سفارشی پشتیبانی می کند.
اگر برنامه شما از قطعات استفاده نمی کند، اولین کاری که باید انجام دهید این است که هر صفحه در برنامه خود را برای استفاده از یک قطعه انتقال دهید. در این مرحله فعالیت را حذف نمیکنید. در عوض، شما یک قطعه ایجاد می کنید تا نمایشگر را نشان دهد و منطق UI خود را با مسئولیت از هم جدا کند.
معرفی قطعات
برای نشان دادن روند معرفی قطعات، اجازه دهید با مثالی از یک برنامه کاربردی شروع کنیم که از دو صفحه تشکیل شده است: صفحه لیست محصول و صفحه جزئیات محصول . با کلیک بر روی یک محصول در صفحه لیست کاربر را به صفحه جزئیات می برد تا اطلاعات بیشتری در مورد محصول کسب کند.
در این مثال، صفحه لیست و جزئیات در حال حاضر فعالیت های جداگانه ای هستند.
یک Layout جدید برای میزبانی رابط کاربری ایجاد کنید
برای معرفی یک قطعه، با ایجاد یک فایل طرح بندی جدید برای فعالیت شروع به میزبانی قطعه کنید. این جایگزین طرح نمای محتوای فعلی فعالیت می شود.
برای یک نمای ساده، می توانید از 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 فعالیت خود تغییر دهید تا به این فایل طرح بندی جدید اشاره کند:
کاتلین
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
جاوا
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
استفاده می کند، برای مثال:
کاتلین
class ProductListFragment : Fragment() { // Leave empty for now. }
جاوا
public class ProductListFragment extends Fragment { // Leave empty for now. }
منطق فعالیت را به یک قطعه منتقل کنید
با تعریف قطعه، مرحله بعدی این است که منطق رابط کاربری این صفحه را از فعالیت به این قطعه جدید منتقل کنید. اگر از یک معماری مبتنی بر فعالیت میآیید، احتمالاً منطق ایجاد view زیادی در تابع onCreate()
فعالیت شما اتفاق میافتد.
در اینجا یک نمونه صفحه نمایش مبتنی بر فعالیت با منطق رابط کاربری وجود دارد که باید آن را جابجا کنیم:
کاتلین
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 } ... }
جاوا
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 }
همانطور که در مثال زیر نشان داده شده است، ممکن است فعالیت شما کنترل کننده زمان و نحوه حرکت کاربر به صفحه بعدی باشد:
کاتلین
// 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) }
جاوا
// 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()
توزیع میکنید و فقط منطق ناوبری در فعالیت باقی میماند:
کاتلین
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) } } ... }
جاوا
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
را میگیرد که میتواند برای افزایش نمای ریشه بر اساس فایل منبع طرحبندی استفاده شود. این مثال از طرحبندی product_list
موجود استفاده میکند که توسط اکتیویتی استفاده شده است، زیرا نیازی به تغییر در خود طرح نیست.
اگر منطق UI در توابع onStart()
، onResume()
، onPause()
یا onStop()
وجود دارد که مربوط به ناوبری نیست، می توانید آن ها را به توابع مربوطه با همان نام در قطعه منتقل کنید.
قطعه را در فعالیت میزبان راه اندازی کنید
هنگامی که تمام منطق UI را به قطعه منتقل کردید، فقط منطق ناوبری باید در فعالیت باقی بماند.
کاتلین
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) } }
جاوا
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()
است، درست پس از تنظیم نمای محتوا:
کاتلین
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() } }
جاوا
@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 باشد باید قطعه را اضافه کنید.
موارد اضافی قصد را به قطعه ارسال کنید
اگر اکتیویتی شما Extras
از طریق intent دریافت می کند، می توانید آنها را مستقیماً به عنوان آرگومان به قطعه ارسال کنید.
در این مثال، ProductDetailsFragment
آرگومانهای خود را مستقیماً از اضافیهای هدف فعالیت دریافت میکند:
کاتلین
... 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() } ...
جاوا
... 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(); } ...
در این مرحله، باید بتوانید اجرای برنامه خود را با اولین صفحه به روز شده برای استفاده از یک قطعه آزمایش کنید. به انتقال بقیه صفحههای مبتنی بر فعالیت خود ادامه دهید و بعد از هر بار تکرار، برای آزمایش زمان صرف کنید.
کامپوننت Navigation را یکپارچه کنید
هنگامی که از یک معماری مبتنی بر قطعه استفاده می کنید، آماده شروع یکپارچه سازی مولفه Navigation هستید.
ابتدا، با پیروی از دستورالعملهای یادداشتهای انتشار کتابخانه ناوبری ، جدیدترین وابستگیهای ناوبری را به پروژه خود اضافه کنید.
یک نمودار ناوبری ایجاد کنید
مؤلفه Navigation پیکربندی پیمایش برنامه شما را در یک فایل منبع به صورت نمودار نشان میدهد، دقیقاً مانند نمایشهای برنامه شما. این به سازماندهی ناوبری برنامه شما در خارج از پایگاه کد کمک می کند و راهی را برای شما فراهم می کند تا پیمایش برنامه خود را به صورت بصری ویرایش کنید.
برای ایجاد یک نمودار ناوبری، با ایجاد یک پوشه منبع جدید به نام navigation
شروع کنید. برای افزودن نمودار، روی این دایرکتوری کلیک راست کرده و New > Navigation resource file را انتخاب کنید.
مؤلفه Navigation از یک فعالیت به عنوان میزبان برای پیمایش استفاده میکند و تکههای جداگانه را در آن میزبان مبادله میکند، زیرا کاربران شما در برنامه شما پیمایش میکنند. قبل از اینکه بتوانید به صورت بصری ناوبری برنامه خود را چیدمان کنید، باید یک NavHost
در داخل فعالیتی که قرار است میزبان این نمودار باشد، پیکربندی کنید. از آنجایی که ما از قطعات استفاده می کنیم، می توانیم از اجرای پیش فرض NavHost
مولفه Navigation، 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
به نمودار ناوبری مرتبط با این میزبان ناوبری اشاره می کند. تنظیم این ویژگی، نمودار nav را افزایش می دهد و ویژگی گراف را در 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>
اگر روی تب Design در پایین کلیک کنید، باید نموداری شبیه به تصویر زیر مشاهده کنید. در سمت چپ بالای نمودار، در قسمت Destinations ، میتوانید مرجعی به فعالیت NavHost
به شکل layout_name (resource_id)
ببینید.
روی دکمه پلاس کلیک کنید نزدیک به بالا برای اضافه کردن قطعات خود به این نمودار.
مؤلفه ناوبری به صفحه های جداگانه به عنوان مقصد اشاره دارد. مقاصد می توانند قطعات، فعالیت ها یا مقاصد سفارشی باشند. میتوانید هر نوع مقصدی را به نمودار خود اضافه کنید، اما توجه داشته باشید که مقصد فعالیتها مقصد پایانی در نظر گرفته میشوند، زیرا هنگامی که به مقصد فعالیت پیمایش میکنید، در یک میزبان ناوبری و نمودار جداگانه فعالیت میکنید.
مولفه ناوبری به روشی اشاره دارد که در آن کاربران از یک مقصد به مقصد دیگر به عنوان کنش می آیند. کنشها همچنین میتوانند انیمیشنهای انتقالی و رفتار پاپ را توصیف کنند.
تراکنش های قطعه را حذف کنید
اکنون که از مؤلفه Navigation استفاده می کنید، اگر در حال پیمایش بین صفحه های مبتنی بر قطعه تحت یک فعالیت هستید، می توانید تعاملات FragmentManager
را حذف کنید.
اگر برنامه شما از چندین قطعه تحت یک فعالیت یا پیمایش سطح بالا مانند طرح بندی کشو یا پیمایش پایین استفاده می کند، احتمالاً از FragmentManager
و FragmentTransactions
برای افزودن یا جایگزینی قطعات در بخش محتوای اصلی رابط کاربری خود استفاده می کنید. اکنون می توان با استفاده از مولفه Navigation با ارائه اقداماتی برای پیوند دادن مقصدها در نمودار خود و سپس پیمایش با استفاده از NavController
جایگزین و ساده کرد.
در اینجا چند سناریو وجود دارد که ممکن است با آنها روبرو شوید و همچنین نحوه برخورد شما با مهاجرت برای هر سناریو وجود دارد.
فعالیت منفرد که چندین قطعه را مدیریت می کند
اگر یک اکتیویتی دارید که چندین قطعه را مدیریت می کند، کد فعالیت شما ممکن است به شکل زیر باشد:
کاتلین
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() } }
جاوا
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(); } }
در داخل مقصد مبدا، ممکن است یک تابع ناوبری را در پاسخ به برخی رویدادها فراخوانی کنید، همانطور که در زیر نشان داده شده است:
کاتلین
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) } }
جاوا
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>
سپس، می توانید فعالیت خود را به روز کنید:
کاتلین
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) } }
جاوا
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
برای رفتن به صفحه جزئیات محصول بعدی استفاده کنیم.
آرگومان ها را با خیال راحت بگذرانید
مؤلفه Navigation دارای یک پلاگین Gradle به نام Safe Args است که کلاس های شی ساده و سازنده را برای دسترسی ایمن نوع به آرگومان های مشخص شده برای مقصدها و اقدامات ایجاد می کند.
هنگامی که افزونه اعمال می شود، هر آرگومان تعریف شده در یک مقصد در نمودار ناوبری شما باعث می شود که چارچوب جزء ناوبری یک کلاس Arguments
ایجاد کند که آرگومان های ایمن نوع را برای مقصد مورد نظر ارائه می دهد. تعریف یک عمل باعث میشود که افزونه یک کلاس پیکربندی Directions
ایجاد کند که میتواند به NavController
بگوید چگونه کاربر را به مقصد مورد نظر هدایت کند. وقتی یک عمل به مقصدی اشاره می کند که به آرگومان نیاز دارد، کلاس Directions
تولید شده شامل متدهای سازنده است که به آن پارامترها نیاز دارد.
همانطور که در مثال زیر نشان داده شده است، در داخل قطعه، از NavController
و کلاس Directions
تولید شده برای ارائه آرگومان های ایمن نوع به مقصد مورد نظر استفاده کنید:
کاتلین
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) } }
جاوا
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
استفاده می کند، ممکن است منطق پیکربندی زیادی در فعالیت خود داشته باشید که باز و بسته کردن کشو و پیمایش به مقصدهای دیگر را مدیریت می کند.
فعالیت شما ممکن است چیزی شبیه به این باشد:
کاتلین
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) }
جاوا
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); } }
پس از اینکه جزء ناوبری را به پروژه خود اضافه کردید و یک نمودار ناوبری ایجاد کردید، هر یک از مقصدهای محتوا را از نمودار خود اضافه کنید (مانند صفحه اصلی ، گالری ، نمایش اسلاید ، و ابزارها از مثال بالا). مطمئن شوید که مقادیر 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
و رفتار مناسب دکمههای Up و Back را انجام میدهد.
سپس MainActivity
شما می تواند برای اتصال NavController
به Toolbar
و NavigationView
به روز شود.
برای نمونه به قطعه زیر مراجعه کنید:
کاتلین
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) } }
جاوا
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 مراجعه کنید.
مقصدهای فعالیت را اضافه کنید
هنگامی که هر صفحه در برنامه شما برای استفاده از مؤلفه Navigation سیمکشی میشود و دیگر از FragmentTransactions
برای انتقال بین مقصدهای مبتنی بر قطعه استفاده نمیکنید، گام بعدی حذف تماسهای startActivity
است.
ابتدا، مکانهایی را در برنامه خود شناسایی کنید که دو نمودار ناوبری مجزا دارید و از startActivity
برای انتقال بین آنها استفاده میکنید.
این مثال شامل دو نمودار (A و B) و یک startActivity()
برای انتقال از A به B است.
کاتلین
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
جاوا
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
سپس، اینها را با یک مقصد فعالیت در نمودار A جایگزین کنید که نشان دهنده مسیریابی به فعالیت میزبان نمودار B است. اگر آرگومان هایی برای ارسال به مقصد شروع نمودار B دارید، می توانید آنها را در تعریف مقصد فعالیت مشخص کنید.
در مثال زیر، نمودار A یک مقصد فعالیت را تعریف میکند که آرگومان product_id
را همراه با یک اقدام میگیرد. نمودار B هیچ تغییری ندارد.
نمایش 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>
میتوانید با استفاده از مکانیزمهای مشابهی که برای پیمایش به مقاصد قطعهسازی استفاده میکنید، به فعالیت میزبان نمودار B بروید:
کاتلین
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
جاوا
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
ارگهای مقصد فعالیت را به قطعه مقصد شروع منتقل کنید
اگر اکتیویتی مقصد موارد اضافی دریافت می کند، مانند مثال قبلی، می توانید آنها را مستقیماً به عنوان آرگومان به مقصد شروع ارسال کنید، اما باید نمودار ناوبری میزبان خود را به صورت دستی در متد onCreate()
اکتیویتی میزبان تنظیم کنید تا بتوانید هدف را ارسال کنید. موارد اضافی به عنوان آرگومان های قطعه، همانطور که در زیر نشان داده شده است:
کاتلین
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 = navHostFragment.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
جاوا
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()); } }
همانطور که در مثال زیر نشان داده شده است، می توان داده ها را با استفاده از کلاس args تولید شده از آرگومان های قطعه Bundle
خارج کرد:
کاتلین
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
جاوا
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
ساده که حاوی یک قطعه است. در بیشتر این موارد، شما فقط می توانید تمام عناصر را از هر نمودار ناوبری ترکیب کنید و هر عنصر مقصد فعالیت را به مقصدهای قطعه قطعه به روز کنید.
مثال زیر نمودارهای 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 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
نشان می دهد:
کاتلین
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
جاوا
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
منابع اضافی
برای اطلاعات بیشتر مرتبط با ناوبری، به موضوعات زیر مراجعه کنید:
- بهروزرسانی اجزای رابط کاربری با NavigationUI - نحوه مدیریت ناوبری با نوار برنامه بالا، کشوی پیمایش و پیمایش پایین را بیاموزید
- Test Navigation - نحوه آزمایش گردش کار ناوبری برای برنامه خود را بیاموزید