Compose页面切换的几种方式:Navigation、NavigationBar+HorizontalPager,会导致LaunchedEffect执行?

比如,我们要做成底部导航栏去动态切换。

kt 复制代码
// 1. 定义密封类或对象来存储目的地信息
sealed class BottomNavItem(
    val route: String,
    val icon: ImageVector,
    val title: String
) {
    object Home : BottomNavItem("home", Icons.Default.Home, "Home")
    object Search : BottomNavItem("search", Icons.Default.Search, "Search")
    object Profile : BottomNavItem("profile", Icons.Default.Person, "Profile")
}

// 2. 在主界面 Scaffold 中设置
@Composable
fun MainScreen() {
    val navController = rememberNavController()
    val currentBackStack by navController.currentBackStackEntryAsState()
    val currentDestination = currentBackStack?.destination

    Scaffold(
        bottomBar = {
            NavigationBar {
                // 获取所有底部导航项
                val navItems = listOf(
                    BottomNavItem.Home,
                    BottomNavItem.Search,
                    BottomNavItem.Profile
                )
                navItems.forEach { item ->
                    NavigationBarItem(
                        // 高亮选中当前目的地对应的Item
                        selected = currentDestination?.route == item.route,
                        onClick = {
                            // 导航到该目的地,并采用弹出到起始点的策略
                            // 避免在点击底部栏Item时在栈中堆积多个同一目的地的实例
                            navController.navigate(item.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                // 如果点击的是当前已选中的Item,则避免重复导航
                                launchSingleTop = true
                                // 恢复之前保存的状态(如果存在)
                                restoreState = true
                            }
                        },
                        icon = { Icon(item.icon, contentDescription = item.title) },
                        label = { Text(item.title) }
                    )
                }
            }
        }
    ) { innerPadding ->
        // 3. 设置 NavHost
        NavHost(
            navController = navController,
            startDestination = BottomNavItem.Home.route,//设置主页
            modifier = Modifier.padding(innerPadding)
        ) {
            // 为每个目的地定义可组合函数
            composable(BottomNavItem.Home.route) { HomeScreen(navController) }
            composable(BottomNavItem.Search.route) { SearchScreen(navController) }
            composable(BottomNavItem.Profile.route) { ProfileScreen(navController) }
        }
    }
}

当然,它不一定需要导航栏,比如有一种情况就是,我们希望通过页面的一些按钮或逻辑来切换,那么我们可以去掉底部导航栏。直接这样就行。

kt 复制代码
@Composable
fun MainScreen() {
    val navController = rememberNavController()
    
        NavHost(
            navController = navController,
            startDestination = BottomNavItem.Home.route,//设置主页
            modifier = Modifier.padding(innerPadding)
        ) {
            // 为每个目的地定义可组合函数
            composable(BottomNavItem.Home.route) { HomeScreen(navController) }
            composable(BottomNavItem.Search.route) { SearchScreen(navController) }
            composable(BottomNavItem.Profile.route) { ProfileScreen(navController) }
        }


}

还有一种页面切换的方式,NavigationBar+HorizontalPager

二、NavigationBar+HorizontalPager

kt 复制代码
@Composable
fun MainScaffold(navHostController: NavHostController) {
    // 创建页面状态(使用Accompanist Pager)
    val pagerState = rememberPagerState(
        pageCount = { MainScreen.values().size } // 页面数量
    )
    // 创建协程作用域
    val scope = rememberCoroutineScope()

    // 当前选中的页面索引(状态)
    var selectedScreen by remember { mutableIntStateOf(0) }

    // 监听页面变化并更新选中状态
    LaunchedEffect(pagerState) {
        // 使用snapshotFlow将页面状态转换为Flow
        snapshotFlow { pagerState.currentPage }.collect { page ->
            selectedScreen = page
        }
    }

    // 使用Scaffold布局
    Scaffold(
        bottomBar = {
            // 自定义底部导航栏
            WanBottomBar(
                selectedScreen = selectedScreen, // 当前选中项
                onClick = { screenIndex ->
                    // 点击导航项时滚动到对应页面
                    scope.launch {
                        pagerState.scrollToPage(screenIndex)
                    }
                }
            )
        }
    ) { innerPadding ->
        // 水平分页器(实现页面滑动)
        HorizontalPager(
            state = pagerState,
            userScrollEnabled = false, // 禁用用户滑动(仅通过底部导航切换)
            contentPadding = innerPadding // 内边距
        ) { pageIndex ->
            // 根据页面索引显示对应内容
            when (MainScreen.values()[pageIndex]) {
                MainScreen.Home -> HomeScreen(navHostController) // 首页
                MainScreen.Project -> ProjectScreen() // 项目页
                MainScreen.Navigator -> NavigatorScreen() // 导航页
                MainScreen.Group -> GroupScreen() // 分组页
                else -> ProfileScreen() // 个人中心页
            }
        }
    }
}

三、这两种方式的区别在哪里呢?

我们可以类比一下原生的

  • 方案一 (Navigation Component)​ ​ 类似于使用 ​​一个 Activity + 多个 Fragment​ ​,并且用 ​NavHostFragment+ 导航图​​ 来管理它们。当你导航时,发生的是 Fragment 事务(Transaction),当前 Fragment 被替换,Fragment就会被销毁了。

  • ​方案二 (Pager)​ ​ 类似于使用 ​​一个 Activity + 一个 ViewPager2​ ​,并且使用 ​FragmentStateAdapter ​ 或 ​FragmentPagerAdapter​。所有 Fragment 都已经被创建并添加到 ViewPager 中,滑动只是切换显示。

所以,切换会导致重新执行吗?

  • ​Navigation 组件​ ​:​​会​ ​。composable { ... }中的代码块会在目的地成为当前目的地时执行,离开时销毁。LaunchedEffect也会。

  • ​HorizontalPager​ ​:​​不会​ ​。每个页面的 Composable只在 HorizontalPager首次进入组合时执行一次,之后不会被重新执行,LaunchedEffect也不会,除非整个 MainScaffold重组。

相关推荐
yuanManGan5 小时前
走进Linux的世界:初识操作系统(Operator System)
android·linux·运维
叶羽西5 小时前
Android15跟踪函数调用关系
android
消失的旧时光-19437 小时前
webView 的canGoBack/goBack 回退栈
android·webview
SHEN_ZIYUAN7 小时前
Flow 责任链模式图解
android
沐怡旸9 小时前
【底层机制】LeakCanary深度解析:从对象监控到内存泄漏分析的完整技术体系
android·面试
又菜又爱coding9 小时前
Android + Flutter打包出来的APK体积太大
android·flutter
LiuYaoheng9 小时前
【Android】Drawable 基础
android·java
Jerry11 小时前
构建 Compose 界面
android
Y多了个想法11 小时前
Linux驱动开发与Android驱动开发
android·linux·驱动开发
Frank_HarmonyOS13 小时前
在 Android 中使用协程(Coroutine)
kotlin