该 导航组件 是一个可以管理复杂导航、过渡动画、深层链接以及应用中屏幕之间编译时检查的参数传递的库。
本文档作为将现有应用迁移到使用导航组件的通用指南。
从高层次来看,迁移涉及以下步骤
将特定于屏幕的 UI 逻辑移出活动 - 将应用的 UI 逻辑移出活动,确保每个活动仅拥有全局导航 UI 组件(如
Toolbar
)的逻辑,同时将每个屏幕的实现委托给片段或自定义目标。集成导航组件 - 对于每个活动,构建一个导航图,其中包含该活动管理的一个或多个片段。用导航组件操作替换片段事务。
添加活动目标 - 用使用活动目标的操作替换
startActivity()
调用。组合活动 - 在多个活动共享公共布局的情况下组合导航图。
先决条件
本指南假设您已迁移应用以使用 AndroidX 库。如果您尚未这样做,请 迁移您的项目 以使用 AndroidX,然后再继续。
将特定于屏幕的 UI 逻辑移出活动
活动是系统级组件,它促进了应用与 Android 之间的图形交互。活动在应用的清单中注册,以便 Android 知道哪些活动可供启动。活动类使应用能够对 Android 的更改做出反应,例如当应用的 UI 进入或离开前台、旋转等。活动还可以用作 在屏幕之间共享状态 的位置。
在应用的上下文中,活动应充当导航的主机,并应保存如何转换屏幕、传递数据等方面的逻辑和知识。但是,最好将 UI 的详细信息管理委托给 UI 的较小、可重用的部分。此模式的推荐实现是 片段。请参阅 单一活动:为什么、何时以及如何,以了解有关使用片段优势的更多信息。导航通过 navigation-fragment 依赖项支持片段。导航还支持 自定义目标类型。
如果您的应用没有使用 Fragment,那么您需要做的第一件事就是将应用中的每个屏幕迁移到使用 Fragment。此时您无需移除 Activity。相反,您将创建一个 Fragment 来表示屏幕,并根据职责将 UI 逻辑分解。
引入 Fragment
为了说明引入 Fragment 的过程,让我们以一个包含两个屏幕的应用程序为例:产品列表屏幕和产品详情屏幕。点击列表屏幕中的某个产品会将用户带到详情屏幕,以便了解更多关于该产品的信息。
在此示例中,列表和详情屏幕目前是单独的 Activity。
创建用于承载 UI 的新布局
要引入 Fragment,首先为 Activity 创建一个新的布局文件来承载 Fragment。这将替换 Activity 当前的内容视图布局。
对于简单的视图,您可以使用 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
属性引用了稍后添加 Fragment 的内容部分。
接下来,在 Activity 的 onCreate()
函数中,修改 Activity 的 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
)将用作您即将创建的 Fragment 的根视图。
创建片段
创建一个新的 Fragment 来管理屏幕的 UI。与 Activity 宿主名称保持一致是一个好习惯。例如,下面的代码片段使用了 ProductListFragment
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
将 Activity 逻辑移动到 Fragment 中
在 Fragment 定义就位后,下一步是将此屏幕的 UI 逻辑从 Activity 移动到此新的 Fragment 中。如果您来自基于 Activity 的架构,那么您可能在 Activity 的 onCreate()
函数中执行了许多视图创建逻辑。
这是一个需要移动 UI 逻辑的基于 Activity 的屏幕示例
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 }
您的 Activity 也可能控制用户何时以及如何导航到下一个屏幕,如下例所示
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); }
在 Fragment 内部,您将此工作分配到 onCreateView()
和 onViewCreated()
中,仅将导航逻辑保留在 Activity 中
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()
来加载和连接布局。在 Fragment 中,onCreateView()
初始化根视图。 onCreateView()
获取 LayoutInflater
的实例,该实例可用于基于布局资源文件加载根视图。此示例重新使用了 Activity 使用的现有 product_list
布局,因为布局本身无需更改。
如果您的 Activity 的 onStart()
、onResume()
、onPause()
或 onStop()
函数中存在任何与导航无关的 UI 逻辑,则可以将其移动到 Fragment 中同名函数。
在宿主 Activity 中初始化 Fragment
将所有 UI 逻辑都移到 Fragment 后,Activity 中应该只剩下导航逻辑。
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()
中创建 Fragment 的实例,就在设置内容视图之后
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
会自动在配置更改期间保存和恢复 Fragment,因此您只需要在 savedInstanceState
为 null 时添加 Fragment。
将 Intent 附加数据传递给 Fragment
如果您的 Activity 通过 Intent 接收 Extras
,则可以直接将其作为参数传递给 Fragment。
在此示例中,ProductDetailsFragment
直接从 Activity 的 Intent 附加数据接收其参数
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(); } ...
此时,您应该能够测试使用 Fragment 更新后的第一个屏幕运行您的应用。继续迁移其余基于 Activity 的屏幕,并在每次迭代后留出时间进行测试。
集成 Navigation 组件
一旦开始使用基于 Fragment 的架构,您就可以开始集成 Navigation 组件了。
首先,按照Navigation 库发行说明中的说明,将最新的 Navigation 依赖项添加到您的项目中。
创建导航图
Navigation 组件将应用的导航配置表示为资源文件中的一个图,就像表示应用的视图一样。这有助于将应用的导航组织在代码库之外,并提供一种以可视方式编辑应用导航的方法。
要创建导航图,首先创建一个名为 navigation
的新资源文件夹。要添加图,请右键单击此目录,然后选择新建 > 导航资源文件。
Navigation 组件使用 Activity 作为导航宿主,并在用户浏览应用时将单个 Fragment 交换到该宿主中。在开始以可视方式布局应用的导航之前,您需要在将承载此图的 Activity 中配置一个 NavHost
。由于我们使用的是 Fragment,因此我们可以使用 Navigation 组件的默认 NavHost
实现 NavHostFragment
。
可以通过放置在宿主 Activity 内部的 FragmentContainerView
配置 NavHostFragment
,如下例所示
<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 更新 UI 组件以获取示例。
对于简单的布局,您可以将此 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
Activity 的引用,形式为 layout_name (resource_id)
。
单击顶部的加号按钮 将您的 Fragment 添加到此图。
Navigation 组件将各个屏幕称为目标。目标可以是 Fragment、Activity 或自定义目标。您可以将任何类型的目标添加到图中,但请注意,Activity 目标被视为终端目标,因为一旦导航到 Activity 目标,您就在单独的导航宿主和图中操作。
Navigation 组件将用户从一个目标到另一个目标的方式称为操作。操作还可以描述转换动画和弹出行为。
删除 Fragment 事务
现在您正在使用 Navigation 组件,如果您在同一 Activity 下的基于 Fragment 的屏幕之间导航,则可以删除 FragmentManager
交互。
如果您的应用在同一 Activity 下使用多个 Fragment 或使用顶级导航(例如抽屉布局或底部导航),那么您可能正在使用 FragmentManager
和 FragmentTransactions
在 UI 的主要内容部分添加或替换 Fragment。现在可以通过提供操作来链接图中的目标,然后使用 NavController
进行导航,从而替换和简化此操作。
以下是一些您可能会遇到的场景以及如何处理每个场景的迁移。
单个 Activity 管理多个 Fragment
如果有一个 Activity 管理多个 Fragment,则 Activity 代码可能如下所示
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>
然后,您可以更新 Activity
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); } }
Activity 不再需要 navigateToProductDetail()
方法。在下一节中,我们将更新 ProductListFragment
以使用 NavController
导航到下一个产品详情屏幕。
安全地传递参数
Navigation 组件有一个名为Safe Args 的 Gradle 插件,该插件会为目标和操作指定的参数生成简单的对象和构建器类,以便类型安全地访问这些参数。
应用插件后,导航图中目标位置上定义的任何参数都会导致 Navigation 组件框架生成一个 Arguments
类,该类为目标位置提供类型安全的参数。定义一个操作会导致插件生成一个 Directions
配置类,该类可用于告诉 NavController
如何引导用户导航到目标位置。当一个操作指向需要参数的目标位置时,生成的 Directions
类包含需要这些参数的构造方法。
在片段内部,使用 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
,您可能在 Activity 中有很多管理打开和关闭抽屉以及导航到其他位置的配置逻辑。
您生成的 Activity 可能如下所示。
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); } }
在将 Navigation 组件添加到您的项目并创建导航图之后,添加图中的每个内容目标(例如,上面示例中的 Home、Gallery、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
值匹配,那么您可以将此 Activity 的 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 更新 UI 组件。
添加 Activity 目标
一旦应用程序中的每个屏幕都连接到使用 Navigation 组件,并且您不再使用 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);
接下来,将这些替换为图 A 中表示导航到图 B 的宿主 Activity 的 Activity 目标。如果您需要将参数传递到图 B 的起始目标,则可以在 Activity 目标定义中指定它们。
在以下示例中,图 A 定义了一个 Activity 目标,该目标除了操作之外还接收 product_id
参数。图 B 没有更改。
图 A 和 B 的 XML 表示可能如下所示。
<!-- 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 的宿主 Activity。
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);
将 Activity 目标参数传递到起始目标片段
如果目标 Activity 接收额外数据(如前面的示例),您可以将这些数据直接作为参数传递到起始目标,但您需要在宿主 Activity 的 onCreate()
方法中手动设置宿主的导航图,以便您可以将意图额外数据作为参数传递给片段,如下所示。
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()); } }
可以使用生成的 args 类从片段参数 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(); ... } ...
组合 Activity
在多个 Activity 共享相同布局(例如包含单个片段的简单 FrameLayout
)的情况下,您可以组合导航图。在大多数情况下,您只需组合每个导航图中的所有元素,并将任何 Activity 目标元素更新为片段目标。
以下示例组合了上一节中的图 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
内的片段目标,而不是 Activity 目标。
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 更新 UI 组件 - 了解如何使用顶部应用栏、导航抽屉和底部导航管理导航。
- 测试导航 - 了解如何测试应用程序的导航工作流程。