Jetpack Navigation的基本使用
一、基本设置
- 添加依赖
// build.gradle.kts
dependencies {
val nav_version = "2.7.7"
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
// Feature module Support
implementation("androidx.navigation:navigation-dynamic-features-fragment:$nav_version")
// Testing Navigation
androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
}
- 基本配置
<!-- res/navigation/nav_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/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.app.HomeFragment"
android:label="Home">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment"
android:label="Detail">
<argument
android:name="itemId"
app:argType="integer" />
</fragment>
</navigation>
二、基本使用
- Activity设置
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 设置NavController
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// 设置ActionBar
setupActionBarWithNavController(navController)
// 设置底部导航
findViewById<BottomNavigationView>(R.id.bottom_nav)
.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
- Fragment间导航
class HomeFragment : Fragment() {
private val args: HomeFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 使用Safe Args进行导航
binding.button.setOnClickListener {
val action = HomeFragmentDirections
.actionHomeToDetail(itemId = 123)
findNavController().navigate(action)
}
}
}
三、高级用法
- 深层链接(Deep Links)
// 在nav_graph.xml中定义
<fragment
android:id="@+id/detailFragment"
android:name="com.example.app.DetailFragment">
<deepLink
app:uri="example://details/{itemId}" />
<argument
android:name="itemId"
app:argType="integer" />
</fragment>
// 在AndroidManifest.xml中注册
<activity android:name=".MainActivity">
<nav-graph android:value="@navigation/nav_graph" />
</activity>
// 创建深层链接
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.detailFragment)
.setArguments(bundleOf("itemId" to 123))
.createPendingIntent()
- 共享元素转场
// 在Fragment中设置
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.imageView.setOnClickListener { imageView ->
val extras = FragmentNavigatorExtras(
imageView to "shared_image"
)
findNavController().navigate(
R.id.action_home_to_detail,
null,
null,
extras
)
}
}
四、常见问题及解决方案
- 配置变更导致的状态丢失
// 解决方案:使用ViewModel保存状态
class SharedViewModel : ViewModel() {
private val _selectedItem = MutableStateFlow<Item?>(null)
val selectedItem: StateFlow<Item?> = _selectedItem.asStateFlow()
fun selectItem(item: Item) {
_selectedItem.value = item
}
}
// 在Fragment中使用
class HomeFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
sharedViewModel.selectedItem.collect { item ->
// 更新UI
}
}
}
}
- 返回栈管理
// 自定义返回栈行为
findNavController().navigate(R.id.action_home_to_detail) {
// 清除返回栈
popUpTo(R.id.homeFragment) {
inclusive = true
}
// 避免重复创建目标Fragment
launchSingleTop = true
}
// 处理系统返回键
override fun onBackPressed() {
val navController = findNavController(R.id.nav_host_fragment)
if (navController.currentDestination?.id == R.id.homeFragment) {
finish()
} else {
super.onBackPressed()
}
}
- Fragment重叠问题
// 在布局文件中正确设置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="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
- 参数传递问题
// 使用Safe Args进行类型安全的参数传递
// 在nav_graph.xml中定义参数
<fragment android:id="@+id/detailFragment">
<argument
android:name="user"
app:argType="com.example.User"
app:nullable="false" />
</fragment>
// 自定义类型转换器
@Parcelize
data class User(
val id: Int,
val name: String
) : Parcelable
// 在Fragment中使用
val action = HomeFragmentDirections
.actionHomeToDetail(user = user)
findNavController().navigate(action)
五、性能优化
- 懒加载实现
class HomeFragment : Fragment() {
private var isDataLoaded = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 使用viewLifecycleOwner确保正确的生命周期
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
if (!isDataLoaded) {
loadData()
isDataLoaded = true
}
}
}
}
}
- 共享ViewModel优化
// 使用SavedStateHandle保存状态
class NavViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _uiState = savedStateHandle.getStateFlow("ui_state", UiState())
val uiState = _uiState.asStateFlow()
fun updateState(newState: UiState) {
savedStateHandle["ui_state"] = newState
}
}
六、测试
@RunWith(AndroidJUnit4::class)
class NavigationTest {
@Test
fun testNavigation() {
val scenario = launchFragmentInContainer<HomeFragment>()
// 验证导航
onView(withId(R.id.button_navigate))
.perform(click())
// 验证目标Fragment是否显示
onView(withId(R.id.detail_container))
.check(matches(isDisplayed()))
}
}
七、最佳实践
- 单Activity多Fragment架构
// 使用Navigation组件实现单Activity架构
class MainActivity : AppCompatActivity() {
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment)
.navController
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 全局配置
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.loginFragment -> hideBottomNav()
else -> showBottomNav()
}
}
}
}
- 模块化导航
// 在feature模块中定义导航图
// feature_home/src/main/res/navigation/home_graph.xml
<navigation
android:id="@+id/home_graph"
app:startDestination="@id/homeFragment">
...
</navigation>
// 在app模块中包含feature导航图
// app/src/main/res/navigation/nav_graph.xml
<navigation
android:id="@+id/nav_graph"
app:startDestination="@id/home_graph">
<include app:graph="@navigation/home_graph" />
</navigation>
通过以上实践,可以:
- 实现清晰的导航架构
- 处理常见的导航问题
- 优化应用性能
- 实现模块化导航
- 确保导航的可测试性
结合compose的使用
一、基本设置
- 添加依赖
// build.gradle.kts
dependencies {
val nav_version = "2.7.7"
implementation("androidx.navigation:navigation-compose:$nav_version")
}
二、基本使用
- 首先是应用入口 MainActivity.kt:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
// 设置Surface作为应用的根容器
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
// 调用应用的根导航组件
AppNavigation()
}
}
}
}
}
- 导航管理 AppNavigation.kt:
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Screen.Home.route // 使用密封类管理路由
) {
composable(Screen.Home.route) {
HomeScreen(
onNavigateToDetail = { itemId ->
navController.navigate(Screen.Detail.createRoute(itemId))
}
)
}
composable(
route = Screen.Detail.route,
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(
itemId = itemId,
onNavigateBack = {
navController.popBackStack()
}
)
}
}
}
// 使用密封类管理路由
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{itemId}") {
fun createRoute(itemId: Int) = "detail/$itemId"
}
}
- 首页界面 HomeScreen.kt:
@Composable
fun HomeScreen(
onNavigateToDetail: (Int) -> Unit
) {
val items = remember { // 示例数据
List(10) { index ->
Item(id = index, title = "Item $index")
}
}
Column(modifier = Modifier.fillMaxSize()) {
TopAppBar(
title = { Text("首页") }
)
LazyColumn {
items(items) { item ->
ItemCard(
item = item,
onItemClick = { onNavigateToDetail(item.id) }
)
}
}
}
}
@Composable
fun ItemCard(
item: Item,
onItemClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable(onClick = onItemClick),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = item.title,
style = MaterialTheme.typography.headlineSmall
)
}
}
}
data class Item(
val id: Int,
val title: String
)
- 详情页面 DetailScreen.kt:
@Composable
fun DetailScreen(
itemId: Int,
onNavigateBack: () -> Unit
) {
Scaffold(
topAppBar = {
TopAppBar(
title = { Text("详情") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, "返回")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
) {
Text(
text = "商品ID: $itemId",
style = MaterialTheme.typography.headlineMedium
)
// 详情页其他内容
Spacer(modifier = Modifier.height(16.dp))
Text("这是商品 $itemId 的详细信息")
}
}
}
- 主题设置 Theme.kt:
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
- build.gradle (app):
dependencies {
def compose_version = "1.5.0"
def nav_version = "2.7.0"
implementation "androidx.core:core-ktx:1.10.0"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.1.0"
implementation "androidx.navigation:navigation-compose:$nav_version"
// ... 其他依赖
}
使用说明:
- 启动应用流程:
MainActivity
→ setContent
→ MyAppTheme
→ AppNavigation
→ HomeScreen
- 导航操作:
// 导航到详情页
navController.navigate(Screen.Detail.createRoute(itemId))
// 返回上一页
navController.popBackStack()
// 带参数导航
navController.navigate("detail/123")
// 清空回退栈并导航
navController.navigate(Screen.Home.route) {
popUpTo(Screen.Home.route) { inclusive = true }
}
- 获取导航参数:
// 在composable中
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
// 或者使用ViewModel
class DetailViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
val itemId: Int = checkNotNull(savedStateHandle["itemId"])
}
- 深层链接支持:
AndroidManifest.xml:
<activity>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="example.com" />
</intent-filter>
</activity>
高级用法:
- 带动画的导航:
composable(
route = Screen.Detail.route,
enterTransition = { slideInHorizontally() },
exitTransition = { slideOutHorizontally() }
) {
DetailScreen(...)
}
- 共享元素转场:
composable(
route = Screen.Detail.route,
enterTransition = {
fadeIn(animationSpec = tween(700))
}
) {
DetailScreen(...)
}
- 导航生命周期感知:
LaunchedEffect(navController) {
navController.currentBackStackEntryFlow.collect { entry ->
// 处理导航状态变化
}
}
这个完整示例展示了:
- 基本的导航结构
- 参数传递
- 界面切换
- 主题设置
- 代码组织方式
通过这种结构,您可以:
- 轻松管理多个页面
- 实现页面间的参数传递
- 处理返回栈
- 实现深层链接
- 添加页面转场动画
更详细使用说明
通常我们会创建一个单独的文件来管理所有的路由定义:
- 创建路由管理文件 navigation/Screen.kt:
// 定义所有页面路由
sealed class Screen(val route: String) {
// 首页路由
object Home : Screen("home")
// 详情页路由,带参数
object Detail : Screen("detail/{itemId}") {
// 创建带实际参数的路由
fun createRoute(itemId: Int) = "detail/$itemId"
}
// 设置页路由
object Settings : Screen("settings")
// 带多个参数的商品页面路由
object Product : Screen("product/{categoryId}/{productId}") {
fun createRoute(categoryId: Int, productId: Int) =
"product/$categoryId/$productId"
}
// 带可选参数的搜索页面
object Search : Screen("search?query={query}") {
fun createRoute(query: String = "") =
"search?query=$query"
}
}
- 在导航图中使用这些路由 navigation/AppNavigation.kt:
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
// 使用定义好的路由
startDestination = Screen.Home.route
) {
// 首页
composable(Screen.Home.route) {
HomeScreen(
onNavigateToDetail = { itemId ->
// 使用createRoute创建带参数的路由
navController.navigate(Screen.Detail.createRoute(itemId))
},
onNavigateToSettings = {
navController.navigate(Screen.Settings.route)
}
)
}
// 详情页
composable(
route = Screen.Detail.route,
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(
itemId = itemId,
onNavigateBack = {
navController.popBackStack()
}
)
}
// 设置页
composable(Screen.Settings.route) {
SettingsScreen()
}
// 商品页面(多参数)
composable(
route = Screen.Product.route,
arguments = listOf(
navArgument("categoryId") { type = NavType.IntType },
navArgument("productId") { type = NavType.IntType }
)
) { backStackEntry ->
val categoryId = backStackEntry.arguments?.getInt("categoryId") ?: 0
val productId = backStackEntry.arguments?.getInt("productId") ?: 0
ProductScreen(categoryId = categoryId, productId = productId)
}
// 搜索页面(可选参数)
composable(
route = Screen.Search.route,
arguments = listOf(
navArgument("query") {
type = NavType.StringType
defaultValue = ""
nullable = true
}
)
) { backStackEntry ->
val query = backStackEntry.arguments?.getString("query") ?: ""
SearchScreen(query = query)
}
}
}
- 在各个页面中使用导航 screens/HomeScreen.kt:
@Composable
fun HomeScreen(
onNavigateToDetail: (Int) -> Unit,
onNavigateToSettings: () -> Unit
) {
Column {
// 顶部栏
TopAppBar(
title = { Text("首页") },
actions = {
// 设置按钮
IconButton(onClick = onNavigateToSettings) {
Icon(Icons.Default.Settings, "设置")
}
}
)
// 列表
LazyColumn {
items(10) { index ->
ItemCard(
item = Item(id = index, title = "Item $index"),
onClick = { onNavigateToDetail(index) }
)
}
}
}
}
- 处理深层链接 AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 深层链接配置 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="example.com" />
</intent-filter>
</activity>
- 高级导航用法:
// 导航工具类 navigation/NavigationUtils.kt
object NavigationUtils {
// 带选项的导航
fun NavController.navigateWithOptions(
route: String,
options: NavOptions? = null
) {
navigate(route, options)
}
// 清空回退栈并导航
fun NavController.navigateAndClearBackStack(route: String) {
navigate(route) {
popUpTo(graph.startDestinationId) { inclusive = true }
}
}
}
// 使用示例
navController.navigateAndClearBackStack(Screen.Home.route)
- 参数传递的不同方式:
sealed class Screen(val route: String) {
// 路径参数
object Detail : Screen("detail/{itemId}") {
fun createRoute(itemId: Int) = "detail/$itemId"
}
// 查询参数
object Search : Screen("search?query={query}") {
fun createRoute(query: String) = "search?query=$query"
}
// 多参数
object Product : Screen("product/{categoryId}/{productId}") {
fun createRoute(categoryId: Int, productId: Int) =
"product/$categoryId/$productId"
}
}
- 导航状态管理:
@Composable
fun AppNavigation() {
val navController = rememberNavController()
// 监听导航状态
val navBackStackEntry by navController.currentBackStackEntryAsState()
// 获取当前路由
val currentRoute = navBackStackEntry?.destination?.route
// 基于当前路由显示不同的底部导航栏
Scaffold(
bottomBar = {
if (currentRoute != Screen.Detail.route) {
BottomNavigation {
// 底部导航项
}
}
}
) { paddingValues ->
NavHost(
navController = navController,
startDestination = Screen.Home.route,
modifier = Modifier.padding(paddingValues)
) {
// 导航图定义
}
}
}
这种路由管理方式的优势:
-
类型安全:
- 使用密封类确保路由名称的正确性
- 编译时检查路由参数
-
集中管理:
- 所有路由定义在一个地方
- 易于维护和修改
-
参数处理:
- 清晰的参数传递方式
- 支持多种参数类型
-
可扩展性:
- 易于添加新的路由
- 支持复杂的导航场景
-
代码复用:
- 避免重复的路由字符串
- 提供统一的路由创建方法
通过这种方式,您可以更好地组织和管理应用的导航逻辑。
三、高级用法
- 嵌套导航
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
// 主导航图
navigation(
startDestination = "home",
route = "main"
) {
composable("home") { HomeScreen() }
composable("profile") { ProfileScreen() }
}
// 认证导航图
navigation(
startDestination = "login",
route = "auth"
) {
composable("login") { LoginScreen() }
composable("register") { RegisterScreen() }
}
}
}
- 带参数的导航
// 定义导航路由
sealed class Screen(val route: String) {
object Home : Screen("home")
object Detail : Screen("detail/{itemId}") {
fun createRoute(itemId: Int) = "detail/$itemId"
}
}
// 使用导航
NavHost(navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) {
HomeScreen(
onNavigateToDetail = { itemId ->
navController.navigate(Screen.Detail.createRoute(itemId))
}
)
}
composable(
route = Screen.Detail.route,
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(itemId = itemId)
}
}
- 深层链接支持
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable(
route = "detail/{itemId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "example://details/{itemId}"
}
)
) { backStackEntry ->
DetailScreen(
itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
)
}
}
}
四、状态管理
- 使用ViewModel
@HiltViewModel
class SharedViewModel @Inject constructor() : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState = _uiState.asStateFlow()
fun updateState(newState: UiState) {
_uiState.value = newState
}
}
@Composable
fun AppNavigation(
viewModel: SharedViewModel = hiltViewModel()
) {
val navController = rememberNavController()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(
uiState = uiState,
onEvent = viewModel::handleEvent
)
}
}
}
- 保存状态
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable("home") {
val savedStateHandle = rememberSaveable { mutableStateOf("") }
HomeScreen(savedState = savedStateHandle.value)
}
}
}
五、动画效果
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "home") {
composable(
route = "home",
enterTransition = {
slideIntoContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Left
)
},
exitTransition = {
slideOutOfContainer(
towards = AnimatedContentTransitionScope.SlideDirection.Left
)
}
) {
HomeScreen()
}
}
}
六、常见问题解决方案
- 返回键处理
@Composable
fun AppNavigation() {
val navController = rememberNavController()
// 处理系统返回键
BackHandler {
when {
navController.currentBackStackEntry?.destination?.route == "home" -> {
// 退出应用
}
else -> {
navController.popBackStack()
}
}
}
}
- 导航状态保存
@Composable
fun AppNavigation() {
val navController = rememberNavController()
// 保存导航状态
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
LaunchedEffect(currentRoute) {
// 处理路由变化
}
}
- 参数传递
// 使用Parcelable对象
@Parcelize
data class User(
val id: Int,
val name: String
) : Parcelable
// 在导航中使用
NavHost(navController, startDestination = "home") {
composable(
route = "detail/{userId}",
arguments = listOf(
navArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
val user = remember { User(userId, "User $userId") }
DetailScreen(user = user)
}
}
七、最佳实践
- 路由管理
// 集中管理导航路由
object NavigationDestinations {
const val HOME = "home"
const val DETAIL = "detail/{itemId}"
const val PROFILE = "profile"
fun createDetailRoute(itemId: Int) = "detail/$itemId"
}
// 使用密封类管理导航动作
sealed class NavigationAction {
data class NavigateToDetail(val itemId: Int) : NavigationAction()
object NavigateBack : NavigationAction()
}
// 在ViewModel中处理导航
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
private val _navigation = MutableSharedFlow<NavigationAction>()
val navigation = _navigation.asSharedFlow()
fun navigateToDetail(itemId: Int) {
viewModelScope.launch {
_navigation.emit(NavigationAction.NavigateToDetail(itemId))
}
}
}
- 模块化导航
// 特性模块导航
fun NavGraphBuilder.featureNavigation(
navController: NavController
) {
navigation(
startDestination = "feature_home",
route = "feature"
) {
composable("feature_home") {
FeatureHomeScreen()
}
}
}
// 在主导航图中使用
NavHost(navController, startDestination = "main") {
featureNavigation(navController)
}
- 导航事件处理
@Composable
fun AppNavigation(
viewModel: MainViewModel = hiltViewModel()
) {
val navController = rememberNavController()
// 处理导航事件
LaunchedEffect(Unit) {
viewModel.navigation.collect { action ->
when (action) {
is NavigationAction.NavigateToDetail -> {
navController.navigate(
NavigationDestinations.createDetailRoute(action.itemId)
)
}
NavigationAction.NavigateBack -> {
navController.popBackStack()
}
}
}
}
}
通过以上实践,可以:
- 实现清晰的导航结构
- 优雅地处理导航状态
- 实现模块化导航
- 添加流畅的动画效果
- 有效管理导航事件
这些模式可以帮助你构建一个可维护和可扩展的Compose导航系统。
Navigation底层实现原理
Jetpack Compose Navigation的底层原理和页面栈管理机制:
graph TD
%% NavController核心结构
subgraph NavController
BackStack[导航回退栈<br>Stack<BackStackEntry>]
NavGraph[导航图<br>NavGraph]
NavCtrl[导航控制器<br>NavController]
end
%% 导航操作
subgraph Navigation
Navigate[导航操作<br>navigate]
PopBack[返回操作<br>popBackStack]
Replace[替换操作<br>navigate + popUpTo]
end
%% 状态管理
subgraph State
SavedState[保存状态<br>SavedStateHandle]
Arguments[参数管理<br>Arguments]
LifecycleState[生命周期状态<br>Lifecycle]
end
%% 导航事件流
Navigate --> |1.创建| Entry[BackStackEntry]
Entry --> |2.入栈| BackStack
Entry --> |3.关联| SavedState
Entry --> |4.携带| Arguments
Entry --> |5.管理| LifecycleState
%% 返回操作流
PopBack --> |1.出栈| BackStack
PopBack --> |2.状态清理| SavedState
PopBack --> |3.生命周期变化| LifecycleState
%% 替换操作流
Replace --> |1.清理指定条目| BackStack
Replace --> |2.新条目入栈| BackStack
%% 设置样式
classDef stack fill:#f9f,stroke:#333,stroke-width:2px
classDef nav fill:#2ecc71,stroke:#27ae60,stroke-width:2px,color:white
classDef state fill:#3498db,stroke:#2980b9,stroke-width:2px,color:white
classDef action fill:#f66,stroke:#333,stroke-width:2px
class BackStack,NavGraph,NavCtrl stack
class Navigate,PopBack,Replace nav
class SavedState,Arguments,LifecycleState state
class Entry action
让我详细解释Navigation的底层实现原理:
1. 核心组件结构:
// 1. 导航控制器
class NavController {
// 回退栈
private val backStack = ArrayDeque<BackStackEntry>()
// 导航图
private var graph: NavGraph? = null
// 当前回退栈条目
val currentBackStackEntry: BackStackEntry?
get() = backStack.lastOrNull()
}
// 2. 回退栈条目
class BackStackEntry(
// 目标路由
val destination: NavDestination,
// 参数
val arguments: Bundle?,
// 状态保存
val savedStateHandle: SavedStateHandle,
// 生命周期
val lifecycle: Lifecycle
)
// 3. 导航目标
sealed class NavDestination {
// 路由
val route: String
// 参数定义
val arguments: List<NamedNavArgument>
// 深层链接
val deepLinks: List<NavDeepLink>
}
2. 导航操作实现:
class NavController {
// 导航操作
fun navigate(route: String, navOptions: NavOptions? = null) {
viewModelScope.launch {
// 1. 解析路由
val destination = findDestination(route)
// 2. 处理导航选项
navOptions?.let { options ->
// 处理PopUpTo操作
if (options.popUpTo != null) {
popBackStack(options.popUpTo, options.inclusive)
}
}
// 3. 创建新的回退栈条目
val newEntry = createBackStackEntry(destination, arguments)
// 4. 入栈操作
backStack.addLast(newEntry)
// 5. 通知观察者
_backStackEntries.value = backStack.toList()
}
}
// 返回操作
fun popBackStack(): Boolean {
if (backStack.size <= 1) return false
// 1. 获取当前条目
val currentEntry = backStack.last()
// 2. 执行出栈
backStack.removeLast()
// 3. 清理状态
currentEntry.savedStateHandle.clear()
// 4. 通知观察者
_backStackEntries.value = backStack.toList()
return true
}
}
graph LR
%% 初始状态
subgraph Stack1[初始状态]
A1[首页] --> B1[列表页]
end
%% 导航操作
subgraph Stack2[导航到详情页]
A2[首页] --> B2[列表页] --> C2[详情页]
end
%% 返回操作
subgraph Stack3[返回操作]
A3[首页] --> B3[列表页]
end
%% 替换操作
subgraph Stack4[替换操作]
A4[首页] --> C4[详情页]
end
Stack1 --> |navigate| Stack2
Stack2 --> |popBackStack| Stack3
Stack3 --> |navigate + popUpTo| Stack4
classDef current fill:#f96,stroke:#333,stroke-width:2px
class C2,B3,C4 current
让我用一个简单的例子来解释替换操作:
graph TD
subgraph 示例场景
A[登录页面] --> B[注册页面] --> C[个人信息页]
end
subgraph 替换前的栈
Stack1[栈底] --> Login[登录页]
Login --> Register[注册页]
Register --> Profile[个人信息页<br>当前页面]
end
subgraph 替换后的栈
Stack2[栈底] --> Home[首页<br>替换后]
end
通俗解释:
-
什么是替换操作?
- 相当于"清空指定页面直到某个页面,然后跳转到新页面"
- 类似Android返回键回到首页的效果
-
实际场景举例:
// 场景:用户完成注册后,直接跳转到首页,并清除之前的登录、注册页面
navController.navigate("home") {
// popUpTo表示清除栈直到指定页面
// inclusive = true 表示包含指定页面也清除
popUpTo("login") { inclusive = true }
}
// 执行前的栈:[登录页] -> [注册页] -> [个人信息页]
// 执行后的栈:[首页]
- 常见使用场景:
- 登录成功后跳转到首页
- 支付完成后返回商品列表
- 注册流程完成后清除所有注册页面
就像清空购物车然后放入新商品一样,替换操作就是清空旧页面,放入新页面。
3. 生命周期管理:
class BackStackEntry {
private val _lifecycle = MutableStateFlow<Lifecycle.State>()
val lifecycle: StateFlow<Lifecycle.State> = _lifecycle.asStateFlow()
// 更新生命周期状态
fun updateLifecycle(newState: Lifecycle.State) {
_lifecycle.value = newState
}
}
// 生命周期状态转换
private fun handleDestinationChange() {
// 1. 将当前页面设置为RESUMED
currentBackStackEntry?.updateLifecycle(Lifecycle.State.RESUMED)
// 2. 将上一个页面设置为STARTED
previousBackStackEntry?.updateLifecycle(Lifecycle.State.STARTED)
// 3. 将其他页面设置为CREATED
otherEntries.forEach { it.updateLifecycle(Lifecycle.State.CREATED) }
}
stateDiagram-v2
[*] --> CREATED: 页面创建
CREATED --> STARTED: 页面可见
STARTED --> RESUMED: 页面激活
RESUMED --> STARTED: 页面失去焦点
STARTED --> CREATED: 页面不可见
CREATED --> [*]: 页面销毁
state CREATED {
[*] --> Initialize: 初始化
Initialize --> SaveState: 保存状态
SaveState --> [*]: 完成
}
state STARTED {
[*] --> ViewVisible: 视图可见
ViewVisible --> CollectState: 收集状态
CollectState --> [*]: 完成
}
state RESUMED {
[*] --> Active: 页面激活
Active --> HandleInput: 处理输入
HandleInput --> UpdateUI: 更新UI
UpdateUI --> [*]: 完成
}
4. 状态保存机制:
class SavedStateHandle {
private val regular = mutableMapOf<String, Any?>()
private val savedState = mutableMapOf<String, Any?>()
// 保存状态
fun set(key: String, value: Any?) {
regular[key] = value
}
// 获取状态
fun <T> get(key: String): T? {
return regular[key] as? T ?: savedState[key] as? T
}
// 状态保存
fun saveState(outState: Bundle) {
outState.putAll(regular)
outState.putAll(savedState)
}
}
5. 参数传递机制:
class NavController {
// 参数传递
fun navigate(
route: String,
args: Bundle? = null
) {
val destination = findDestination(route)
// 创建参数Bundle
val finalArgs = Bundle().apply {
// 合并路由参数
putAll(parseRouteArgs(route))
// 合并传入参数
args?.let { putAll(it) }
}
// 创建回退栈条目
val entry = createBackStackEntry(destination, finalArgs)
backStack.addLast(entry)
}
}
6. 深层链接处理:
class NavController {
// 处理深层链接
fun handleDeepLink(intent: Intent) {
val uri = intent.data ?: return
// 查找匹配的目标
val destination = graph.findDestination(uri)
// 提取参数
val args = destination.parseDeepLinkArgs(uri)
// 执行导航
navigate(destination.route, args)
}
}
关键流程说明:
-
导航流程:
- 解析路由
- 创建BackStackEntry
- 处理导航选项
- 执行入栈操作
- 更新生命周期
- 通知观察者
-
返回流程:
- 检查栈状态
- 执行出栈操作
- 清理状态
- 更新生命周期
- 通知观察者
-
状态保存流程:
- 保存页面状态
- 保存参数
- 处理进程死亡恢复
-
生命周期管理:
- CREATED:后台页面
- STARTED:可见未激活
- RESUMED:当前激活页面
这种实现的优势:
-
可靠性:
- 完整的状态保存
- 生命周期管理
- 参数传递安全
-
灵活性:
- 支持复杂导航
- 深层链接支持
- 自定义导航行为
-
性能优化:
- 状态恢复机制
- 内存管理
- 生命周期优化
通过理解这些底层原理,我们可以:
- 更好地使用Navigation组件
- 处理复杂导航场景
- 优化应用性能
- 提供更好的用户体验
使用流程总结
graph TD
A[应用启动] --> B[创建NavController]
B --> C[设置NavHost]
subgraph "导航配置"
C --> D[定义导航图]
D --> E[设置起始目的地]
D --> F[配置路由和参数]
end
subgraph "导航实现"
F --> G[基本导航]
F --> H[带参数导航]
F --> I[嵌套导航]
F --> J[深层链接]
end
subgraph "状态管理"
K[ViewModel] --> L[导航状态]
L --> M[UI状态]
M --> N[状态保存]
end
subgraph "事件处理"
O[用户交互] --> P[导航事件]
P --> Q[ViewModel处理]
Q --> R[执行导航]
end
G --> S[Screen切换]
H --> S
I --> S
J --> S
R --> S
graph TD
subgraph "导航生命周期"
A1[Screen进入] --> A2[获取参数]
A2 --> A3[初始化ViewModel]
A3 --> A4[收集状态]
A4 --> A5[渲染UI]
A5 --> A6[处理返回键]
end
subgraph "参数传递流程"
B1[构建导航参数] --> B2[创建导航路由]
B2 --> B3[执行导航]
B3 --> B4[目标Screen接收参数]
end
subgraph "状态保存流程"
C1[UI状态变化] --> C2[ViewModel更新]
C2 --> C3[SavedStateHandle保存]
C3 --> C4[配置变更恢复]
end
graph TD
subgraph "模块化导航"
D1[主导航模块] --> D2[特性模块A]
D1 --> D3[特性模块B]
D2 --> D4[子导航A]
D3 --> D5[子导航B]
end
subgraph "导航动画"
E1[进入动画] --> E2[退出动画]
E2 --> E3[共享元素转换]
end
sequenceDiagram
participant U as User
participant S as Screen
participant VM as ViewModel
participant NC as NavController
U->>S: 触发导航事件
S->>VM: 调用导航方法
VM->>NC: 发送导航指令
NC->>S: 执行导航
S->>VM: 获取新状态
VM->>S: 更新UI
关键流程说明:
- 初始化流程
- 应用启动
- 创建NavController
- 配置NavHost
- 设置导航图
- 导航执行流程
- 用户触发导航事件
- ViewModel处理导航逻辑
- NavController执行导航
- 目标Screen接收参数并初始化
- 状态管理流程
- ViewModel维护状态
- UI订阅状态更新
- SavedStateHandle保存状态
- 配置变更时恢复状态
- 模块化导航流程
- 主导航模块集成子模块
- 子模块独立管理导航
- 模块间通信通过共享ViewModel
- 参数传递流程
- 构建导航参数
- 创建导航路由
- 执行导航
- 目标接收并处理参数
最佳实践建议:
- 架构设计
- 使用单一职责原则
- 实现清晰的导航层次
- 合理划分导航模块
- 状态管理
- 集中管理导航状态
- 使用ViewModel处理逻辑
- 实现状态持久化
- 性能优化
- 合理使用嵌套导航
- 优化导航动画
- 实现延迟加载
- 测试策略
- 单元测试导航逻辑
- 集成测试导航流程
- 端到端测试用户流程
这些流程图展示了Compose Navigation的主要组件和它们之间的交互关系,有助于理解整个导航系统的工作方式。根据具体需求,可以选择合适的实现方式来构建导航功能。
Navigation的"时空折叠"导航图问题
"时空折叠"导航图是Navigation框架中一个常见的问题,主要指在特定情况下导航栈出现异常,导致用户体验混乱的现象。让我详细解释这个问题及其解决方案。
一、"时空折叠"问题表现
- 重复导航问题
// 问题示例
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("detail") { DetailScreen() }
}
// 用户快速点击或重复点击可能导致
home -> detail -> detail -> detail // 重复创建多个detail页面
- 导航状态紊乱
// 深层嵌套导航时可能出现
A -> B -> C -> A // 形成环形导航
- 返回栈异常
// 返回栈出现异常
home -> detail -> profile -> home // home重复出现在栈中
二、解决方案
1. 使用SingleTop导航模式
// 方案1:使用导航选项
navController.navigate("detail") {
// 避免重复创建同一目的地
launchSingleTop = true
// 可选:清理返回栈
popUpTo("home") {
inclusive = false // false表示保留home
}
}
navController.navigate("profile") {
popUpTo("home") {
inclusive = false
// 保存 home 页面的状态
saveState = true
}
// 恢复 profile 页面的状态
restoreState = true
}
// 方案2:在导航图中定义
NavHost(navController, startDestination = "home") {
composable(
route = "detail",
navigation = {
launchSingleTop = true
}
) {
DetailScreen()
}
}
让我用简单的比喻来解释 singleTop 属性:
想象一下手机里的 App 界面就像一叠纸牌 📑,每打开一个新界面就像在顶部放一张新牌。
singleTop 的作用: 当你要打开一个新界面时,如果这个界面和当前最顶部的界面是同一个类型:
- 如果设置了
singleTop = true:不会创建新界面,直接复用顶部的那个界面 - 如果没设置(默认
false):会创建一个新的界面实例放在顶部
举个生动的例子 🌟: 假设你有一个消息列表界面,点击通知要跳转到这个界面:
// 在 navigation graph 中的设置
<fragment
android:id="@+id/messageListFragment"
android:name=".MessageListFragment"
android:label="消息列表"
tools:layout="@layout/fragment_message_list">
<argument
android:name="messageId"
app:argType="string" />
</fragment>
// 使用方式
findNavController().navigate(R.id.messageListFragment, args, navOptions {
launchSingleTop = true // 设置 singleTop
})
不同情况的表现:
- 没有设置 singleTop(
false):
初始栈:[首页] -> [消息列表]
点击通知后:[首页] -> [消息列表] -> [新的消息列表] // 重复了!
- 设置了 singleTop(
true):
初始栈:[首页] -> [消息列表]
点击通知后:[首页] -> [消息列表] // 复用已有的消息列表界面
使用场景 🎯:
- 通知栏点击跳转
- 底部导航栏切换
- 搜索界面
- 任何不希望重复创建同类界面的场景
好处 ✨:
- 避免界面重复堆叠
- 节省内存
- 提供更好的用户体验
记住一个简单的比喻:就像电梯 🛗,如果你在 5 楼按电梯想去 5 楼,有 singleTop 的电梯会告诉你"你已经在 5 楼了",而没有 singleTop 的电梯会傻傻地再带你转一圈回到 5 楼。
2. 管理导航状态
class NavigationViewModel : ViewModel() {
private val _isNavigating = MutableStateFlow(false)
val isNavigating = _isNavigating.asStateFlow()
fun navigate(navController: NavController, route: String) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
navController.navigate(route)
delay(300) // 防抖
_isNavigating.value = false
}
}
}
}
代码详解
让我详细解释这个导航状态管理的实现和原理:
1. 首先解释这段代码的主要目的:
class NavigationViewModel : ViewModel() {
// 导航状态,防止重复点击导致重复导航
private val _isNavigating = MutableStateFlow(false)
val isNavigating = _isNavigating.asStateFlow()
fun navigate(navController: NavController, route: String) {
viewModelScope.launch {
// 检查是否正在导航中
if (!_isNavigating.value) {
// 设置导航状态为true
_isNavigating.value = true
// 执行导航
navController.navigate(route)
// 延迟300毫秒
delay(300)
// 重置导航状态
_isNavigating.value = false
}
}
}
}
2. 完整的实现示例:
// 1. 扩展的NavigationViewModel
class NavigationViewModel : ViewModel() {
// 导航状态
private val _isNavigating = MutableStateFlow(false)
val isNavigating = _isNavigating.asStateFlow()
// 导航历史
private val _navigationHistory = MutableStateFlow<List<String>>(emptyList())
val navigationHistory = _navigationHistory.asStateFlow()
// 基础导航
fun navigate(navController: NavController, route: String) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
navController.navigate(route)
// 记录导航历史
_navigationHistory.value = _navigationHistory.value + route
delay(300)
_isNavigating.value = false
}
}
}
// 带选项的导航
fun navigateWithOptions(
navController: NavController,
route: String,
builder: NavOptionsBuilder.() -> Unit
) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
navController.navigate(route) {
builder()
}
_navigationHistory.value = _navigationHistory.value + route
delay(300)
_isNavigating.value = false
}
}
}
// 返回上一页
fun popBackStack(navController: NavController) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
navController.popBackStack()
// 更新导航历史
_navigationHistory.value = _navigationHistory.value.dropLast(1)
delay(300)
_isNavigating.value = false
}
}
}
}
// 2. 在Activity或Fragment中使用
class MainActivity : ComponentActivity() {
private val navigationViewModel: NavigationViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
AppNavigation(navigationViewModel)
}
}
}
}
// 3. 导航组件实现
@Composable
fun AppNavigation(navigationViewModel: NavigationViewModel) {
val navController = rememberNavController()
// 收集导航状态
val isNavigating by navigationViewModel.isNavigating.collectAsState()
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
composable(Screen.Home.route) {
HomeScreen(
onNavigateToDetail = { itemId ->
// 使用ViewModel处理导航
navigationViewModel.navigate(
navController,
Screen.Detail.createRoute(itemId)
)
}
)
}
composable(
route = Screen.Detail.route,
arguments = listOf(
navArgument("itemId") { type = NavType.IntType }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailScreen(
itemId = itemId,
onNavigateBack = {
navigationViewModel.popBackStack(navController)
}
)
}
}
// 可选:显示导航状态的加载指示器
if (isNavigating) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
// 4. 页面实现
@Composable
fun HomeScreen(
onNavigateToDetail: (Int) -> Unit
) {
Column {
// 防抖点击处理
Button(
onClick = { onNavigateToDetail(1) },
// 当正在导航时禁用按钮
enabled = !isNavigating
) {
Text("去详情页")
}
}
}
3. 高级用法:
// 1. 带结果的导航
class NavigationViewModel : ViewModel() {
// 导航结果
private val _navigationResult = MutableStateFlow<Any?>(null)
val navigationResult = _navigationResult.asStateFlow()
fun navigateForResult(
navController: NavController,
route: String,
resultKey: String
) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
// 设置结果监听
val resultFlow = navController.currentBackStackEntryFlow
.map { it.savedStateHandle.get<Any>(resultKey) }
.filterNotNull()
// 启动结果收集
launch {
resultFlow.collect { result ->
_navigationResult.value = result
}
}
navController.navigate(route)
delay(300)
_isNavigating.value = false
}
}
}
// 设置导航结果
fun setResult(navController: NavController, key: String, result: Any) {
navController.previousBackStackEntry?.savedStateHandle?.set(key, result)
}
}
// 2. 深层链接处理
class NavigationViewModel : ViewModel() {
fun handleDeepLink(navController: NavController, deepLink: Uri) {
viewModelScope.launch {
if (!_isNavigating.value) {
_isNavigating.value = true
navController.navigate(deepLink)
delay(300)
_isNavigating.value = false
}
}
}
}
4. 状态管理的优势:
-
防止重复导航:
- 通过
isNavigating状态防止用户快速重复点击 - 避免创建多个重复的页面实例
- 通过
-
导航历史追踪:
- 记录导航路径
- 支持复杂的导航回退逻辑
-
结果处理:
- 支持页面间数据传递
- 处理页面返回结果
-
统一管理:
- 集中处理所有导航逻辑
- 便于添加全局导航拦截器
5. 使用场景:
// 1. 普通导航
navigationViewModel.navigate(navController, Screen.Detail.createRoute(1))
// 2. 带选项的导航
navigationViewModel.navigateWithOptions(navController, Screen.Home.route) {
popUpTo(Screen.Home.route) { inclusive = true }
}
// 3. 带结果的导航
navigationViewModel.navigateForResult(
navController,
Screen.Detail.route,
"result_key"
)
// 4. 处理返回结果
LaunchedEffect(navigationViewModel.navigationResult) {
navigationViewModel.navigationResult.collect { result ->
// 处理返回结果
}
}
这种导航状态管理方式的好处:
-
可控性:
- 完全控制导航流程
- 可以添加自定义逻辑
-
可靠性:
- 防止导航异常
- 处理边缘情况
-
可扩展性:
- 易于添加新功能
- 支持复杂导航需求
-
可测试性:
- 导航逻辑集中管理
- 便于单元测试
通过这种方式,您可以更好地控制和管理应用的导航流程,提供更好的用户体验。
3. 清理返回栈
// 在需要清理历史记录的场景(如登录后)
navController.navigate("home") {
// 清除所有返回栈
popUpTo(0) { inclusive = true }
}
// 或者清除到特定目的地
navController.navigate("profile") {
popUpTo("home") {
inclusive = false
// 保存状态
saveState = true
}
// 恢复状态
restoreState = true
}
代码详解
navController.navigate("profile") {
popUpTo("home") {
inclusive = false // 关键参数1
saveState = true // 关键参数2
}
restoreState = true // 关键参数3
}
一、场景举例
假设我们有这样一个导航栈:
首页(home) -> 列表页(list) -> 详情页(detail) -> 个人页(profile)
二、参数解释
- popUpTo("home")
- 这是一个"弹出"操作
- 会清除导航栈直到遇到"home"页面
- 就像把卡片叠在一起,然后从上面移除卡片,直到看到"home"卡片
- inclusive = false
// inclusive = false 时(保留home):
之前:home -> list -> detail -> profile
之后:home -> profile
// inclusive = true 时(移除home):
之前:home -> list -> detail -> profile
之后:profile
- saveState = true
// 保存被移除页面的状态
// 例如:list页面的滚动位置
// 或者:detail页面的表单输入
- restoreState = true
// 当用户返回时,恢复之前保存的状态
// 比如:返回到list页面时,恢复之前的滚动位置
三、具体例子
// 场景1:从购物车跳转到登录页
navController.navigate("login") {
// 清除到首页,但保留首页
popUpTo("home") {
inclusive = false
saveState = true // 保存购物车状态
}
restoreState = true // 登录后返回时恢复状态
}
// 场景2:退出登录
navController.navigate("login") {
// 清除所有页面,包括首页
popUpTo("home") {
inclusive = true // 连home页面一起清除
}
}
四、图解说明
场景1:保留首页(inclusive = false)
操作前:
[首页] -> [列表] -> [详情] -> [当前页]
↓
操作后:
[首页] -> [个人页] (中间页面被移除,但状态被保存)
场景2:不保留首页(inclusive = true)
操作前:
[首页] -> [列表] -> [详情] -> [当前页]
↓
操作后:
[个人页] (所有页面被移除,包括首页)
五、常见使用场景
- 登录后返回
// 登录成功后,清除登录页,回到之前的页面
navController.navigate("previous_page") {
popUpTo("login") {
inclusive = true // 移除登录页
saveState = true // 保存之前页面的状态
}
restoreState = true
}
- 购物流程完成
// 支付完成后,清除购物流程页面,回到首页
navController.navigate("home") {
popUpTo("home") {
inclusive = false // 保留首页
}
}
- 深层链接跳转
// 从通知跳转到特定页面,清除中间页面
navController.navigate("detail/$id") {
popUpTo("home") {
inclusive = false
saveState = true // 保存首页状态
}
restoreState = true
}
六、注意事项
- 状态保存的影响
// 保存状态可能会占用内存
// 对于不需要保存状态的页面,可以设置
saveState = false
- 返回栈管理
// 需要注意返回栈的深度
// 避免保存太多状态导致内存问题
- 性能考虑
// 状态的保存和恢复会有一定性能开销
// 根据实际需求选择是否使用
总结:
popUpTo用于清理导航栈inclusive决定是否包含目标页面saveState和restoreState用于状态管理- 合理使用这些参数可以实现更好的导航体验
- 需要根据具体场景选择合适的参数组合
这些参数的组合使用可以帮助我们实现更复杂的导航需求,同时保持良好的用户体验。
三、预防措施
1. 导航事件防抖
@Composable
fun DebounceButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val debounceTime = 300L
val lastClickTime = remember { mutableStateOf(0L) }
Button(
onClick = {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > debounceTime) {
lastClickTime.value = currentTime
onClick()
}
},
modifier = modifier
) {
content()
}
}
2. 导航状态监控
@Composable
fun NavigationMonitor(navController: NavController) {
// 监控导航状态
val currentBackStack by navController.currentBackStackEntryAsState()
LaunchedEffect(currentBackStack) {
// 记录导航历史
Log.d("Navigation", "Current route: ${currentBackStack?.destination?.route}")
}
}
3. 导航生命周期管理
class NavigationManager {
private val _navigationStack = mutableListOf<String>()
fun addDestination(route: String) {
if (_navigationStack.lastOrNull() != route) {
_navigationStack.add(route)
}
}
fun removeDestination(route: String) {
_navigationStack.remove(route)
}
fun clearStack() {
_navigationStack.clear()
}
}
四、最佳实践
- 规范化导航路由
sealed class NavigationRoute(val route: String) {
object Home : NavigationRoute("home")
object Detail : NavigationRoute("detail/{id}") {
fun createRoute(id: Int) = "detail/$id"
}
}
// 使用
navController.navigate(NavigationRoute.Detail.createRoute(1))
- 集中管理导航行为
class NavigationActions(private val navController: NavController) {
fun navigateToDetail(id: Int) {
navController.navigate(NavigationRoute.Detail.createRoute(id)) {
launchSingleTop = true
popUpTo(NavigationRoute.Home.route) {
saveState = true
}
}
}
fun navigateBack() {
navController.popBackStack()
}
}
- 导航状态恢复
@Composable
fun AppNavigation(
viewModel: NavigationViewModel = hiltViewModel()
) {
val navController = rememberNavController()
// 保存和恢复导航状态
DisposableEffect(navController) {
val callback = NavController.OnDestinationChangedListener { _, destination, _ ->
viewModel.saveLastDestination(destination.route)
}
navController.addOnDestinationChangedListener(callback)
onDispose {
navController.removeOnDestinationChangedListener(callback)
}
}
}
五、测试策略
- 导航测试
@Test
fun testNavigation() {
composeTestRule.setContent {
AppNavigation()
}
// 验证导航行为
composeTestRule.onNodeWithTag("navigate_button").performClick()
composeTestRule.onNodeWithTag("detail_screen").assertIsDisplayed()
}
- 状态恢复测试
@Test
fun testNavigationStateRestoration() {
val scenario = ActivityScenario.launch(MainActivity::class.java)
// 模拟配置变更
scenario.recreate()
// 验证状态恢复
composeTestRule.onNodeWithTag("current_screen")
.assertTextEquals("ExpectedScreen")
}
六、性能优化
- 延迟加载
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable(
route = "detail",
enterTransition = {
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left)
}
) {
// 延迟加载详情页内容
var isLoaded by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
isLoaded = true
}
if (isLoaded) {
DetailScreen()
} else {
LoadingScreen()
}
}
}
通过以上措施,可以有效防止和解决"时空折叠"问题:
- 避免重复导航
- 管理导航状态
- 清理返回栈
- 实现平滑的用户体验
- 确保导航的可预测性
这些解决方案可以帮助构建更稳定、可靠的导航系统。