8.3.1 NavController 的使用
获取 NavController:
kotlin
// 方式1:在 Activity 中获取
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 从 NavHostFragment 获取 NavController
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHostFragment.navController
}
}
// 方式2:使用扩展函数
class MyFragment : Fragment() {
private val navController by lazy {
findNavController()
}
}
// 方式3:使用 View 扩展函数
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = view.findNavController()
}
}
8.3.2 基本导航操作
导航到不同的 Fragment:
kotlin
class HomeFragment : Fragment() {
private val navController by lazy { findNavController() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 导航到用户详情页
btnUserDetail.setOnClickListener {
navController.navigate(R.id.action_homeFragment_to_userDetailFragment)
}
// 导航到设置页
btnSettings.setOnClickListener {
navController.navigate(R.id.action_homeFragment_to_settingsFragment)
}
}
}
8.3.3 数据传递
方式1:使用 Bundle 传递参数:
kotlin
// 发送数据
class HomeFragment : Fragment() {
private val navController by lazy { findNavController() }
fun navigateToUserDetail(userId: String, userName: String) {
val bundle = bundleOf(
"userId" to userId,
"userName" to userName
)
navController.navigate(
R.id.action_homeFragment_to_userDetailFragment,
bundle
)
}
}
// 接收数据
class UserDetailFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 从 arguments 获取数据
val userId = arguments?.getString("userId")
val userName = arguments?.getString("userName")
updateUI(userId, userName)
}
}
方式2:使用 Safe Args(推荐):
gradle
// build.gradle (app\工程)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'androidx.navigation.safeargs.kotlin' // 添加 Safe Args 插件 版本使用2.8.0
}
xml
<!-- 在导航图中定义参数 -->
<fragment
android:id="@+id/userDetailFragment"
android:name="com.example.app.ui.user.UserDetailFragment"
android:label="User Detail">
<argument
android:name="userId"
app:argType="string" />
<argument
android:name="userName"
app:argType="string"
app:nullable="true" />
</fragment>
kotlin
// 发送数据(使用 Safe Args)
class HomeFragment : Fragment() {
private val navController by lazy { findNavController() }
fun navigateToUserDetail(userId: String, userName: String) {
val direction = HomeFragmentDirections
.actionHomeFragmentToUserDetailFragment(userId, userName)
navController.navigate(direction)
}
}
// 接收数据(使用 Safe Args)
class UserDetailFragment : Fragment() {
private val args: UserDetailFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = args.userId
val userName = args.userName
updateUI(userId, userName)
}
}
8.3.4 返回堆栈管理
popUpTo 和 popUpToInclusive:
kotlin
class HomeFragment : Fragment() {
private val navController by lazy { findNavController() }
fun navigateToProfileAndClearBackstack() {
// 导航到个人中心,并清空返回堆栈到首页
navController.navigate(
R.id.action_homeFragment_to_profileFragment
)
}
fun navigateToLoginAndClearBackstack() {
// 导航到登录页,并清空整个返回堆栈
navController.navigate(
R.id.action_homeFragment_to_loginFragment
)
}
}
XML 配置返回堆栈管理:
xml
<action
android:id="@+id/action_home_to_profile"
app:destination="@id/profileFragment"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="false" />
<action
android:id="@+id/action_home_to_login"
app:destination="@id/loginFragment"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
8.4 高级导航功能
8.4.1 深层链接(Deep Link)
深层链接的概念 :
深层链接允许用户直接导航到应用中的特定目的地,无需经过首页。
配置深层链接:
xml
<fragment
android:id="@+id/userDetailFragment"
android:name="com.example.app.ui.user.UserDetailFragment"
android:label="User Detail">
<!-- 深层链接1:app://user/{userId} -->
<deepLink
app:uri="app://user/{userId}"
app:action="android.intent.action.VIEW"
app:mimeType="text/html" />
<!-- 深层链接2:https://www.example.com/user/{userId} -->
<deepLink
app:uri="https://www.example.com/user/{userId}"
app:action="android.intent.action.VIEW"
app:mimeType="text/html" />
<argument
android:name="userId"
app:argType="string" />
</fragment>
在 AndroidManifest.xml 中声明深层链接:
xml
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 深层链接1 -->
<data
android:scheme="app"
android:host="user" />
<!-- 深层链接2 -->
<data
android:scheme="https"
android:host="www.example.com"
android:pathPrefix="/user" />
</intent-filter>
<!-- 导航图 -->
<nav-graph android:value="@navigation/nav_graph" />
</activity>
处理深层链接:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHostFragment.navController
// 处理深层链接
navController.handleDeepLink(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// 处理新的深层链接
navController.handleDeepLink(intent)
}
}
8.4.2 嵌套导航图
嵌套导航图的概念 :
嵌套导航图允许将相关的目的地组织在一起,形成子导航图。
创建嵌套导航图:
xml
<!-- navigation/nested_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nested_graph"
app:startDestination="@id/step1Fragment">
<!-- 步骤1 -->
<fragment
android:id="@+id/step1Fragment"
android:name="com.example.app.ui.onboarding.Step1Fragment"
android:label="Step 1">
<action
android:id="@+id/action_step1_to_step2"
app:destination="@id/step2Fragment" />
</fragment>
<!-- 步骤2 -->
<fragment
android:id="@+id/step2Fragment"
android:name="com.example.app.ui.onboarding.Step2Fragment"
android:label="Step 2">
<action
android:id="@+id/action_step2_to_step3"
app:destination="@id/step3Fragment" />
</fragment>
<!-- 步骤3 -->
<fragment
android:id="@+id/step3Fragment"
android:name="com.example.app.ui.onboarding.Step3Fragment"
android:label="Step 3">
<action
android:id="@+id/action_step3_to_complete"
app:destination="@id/homeFragment"
app:popUpTo="@id/nested_graph"
app:popUpToInclusive="true" />
</fragment>
</navigation>
xml
<!-- 在主导航图中引用嵌套导航图 -->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/onboardingFragment">
<!-- 嵌套导航图 -->
<include app:graph="@navigation/nested_graph" />
<!-- 首页 -->
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.ui.home.HomeFragment"
android:label="Home" />
</navigation>
8.4.3 转场动画
定义转场动画:
xml
<!-- res/anim/slide_in_right.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="100%"
android:toXDelta="0%" />
</set>
<!-- res/anim/slide_out_left.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="-100%" />
</set>
<!-- res/anim/slide_in_left.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="-100%"
android:toXDelta="0%" />
</set>
<!-- res/anim/slide_out_right.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="100%" />
</set>
在导航图中应用转场动画:
xml
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
核心要点回顾
-
导航基础
- 传统导航方式的痛点
- Navigation 组件的优势
-
Navigation Graph 设计
- 导航图的基本结构
- 目的地类型
- 动作配置
-
Fragment 导航实战
- NavController 的使用
- 基本导航操作
- 数据传递(Bundle 和 Safe Args)
- 返回堆栈管理
-
高级导航功能
- 深层链接
- 嵌套导航图
- 转场动画
5使用
并且包含:
- 页面跳转(
navigate) - 参数传递(Safe Args)或者Bundle
- 返回上一页(
navigateUp) - 从详情页跳到设置页并清理部分返回栈(
popUpTo)
1) 添加依赖与插件
gradle
// app/build.gradle //
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'androidx.navigation.safeargs.kotlin'
}
dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:2.8.5"
implementation "androidx.navigation:navigation-ui-ktx:2.8.5"
}
需求背景,动手实践
我们需要构建一个电商应用的导航系统,包含:
- 首页、分类、购物车、我的(底部导航)
- 商品列表、商品详情、搜索(主导航)
- 登录、注册、设置(用户中心)
- 订单列表、订单详情(订单管理)
设计导航结构
导航架构图:
MainActivity (底部导航)
├── HomeFragment (首页)
│ ├── ProductListFragment (商品列表)
│ └── ProductDetailFragment (商品详情)
├── CategoryFragment (分类)
├── CartFragment (购物车)
└── ProfileFragment (我的)
├── LoginFragment (登录)
├── RegisterFragment (注册)
├── SettingsFragment (设置)
└── OrderListFragment (订单列表)
└── OrderDetailFragment (订单详情)
<?xml version="1.0" encoding="utf-8"?>
<!-- 顶部工具栏 -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/purple_500"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<!-- NavHostFragment 容器 -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<!-- 底部导航栏 (可选) -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu" />