news 2026/5/30 10:12:34

Jetpack Compose 多页面架构实战:从 Splash 到底部导航,每个 Tab 拥有独立 ViewModel

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jetpack Compose 多页面架构实战:从 Splash 到底部导航,每个 Tab 拥有独立 ViewModel

在构建现代 Android 应用时,清晰的页面结构和合理的状态管理是保证项目可维护性和可扩展性的关键。Jetpack Compose 提供了声明式 UI 的强大能力,而Navigation+ViewModel的组合,则是实现复杂多页面应用的黄金搭档。

本文将通过一个完整、可运行、生产级风格的案例,带你一步步实现:

  • 启动页(Splash)
  • 登录页(Login)
  • 主页(带底部导航:首页 / 通讯录 / 我的)
  • 每个 Tab 拥有自己独立的 ViewModel
  • 全局登录状态统一管理

并深入探讨页面拆分、导航设计、状态隔离等核心工程实践。

🎯 最终效果预览


所有页面逻辑分离,职责清晰,切换 Tab 不会丢失状态!

📁 推荐项目结构

良好的目录结构是大型项目的基石:

com.example.myapp/├── MyApp.kt// App 根组件├── navigation/NavGraph.kt //全局导航图 ├── viewmodel/AuthViewModel.kt //全局登录状态 ├── route/ModuleRoute.kt //路由常量 └── ui/├── splash/SplashScreen.kt ├── login/LoginScreen.kt └── main/├── MainScreen.kt// 底部导航容器├── home/HomeTab.kt+HomeViewModel.kt ├── contacts/ContactsTab.kt+ContactsViewModel.kt └── profile/ProfileTab.kt+ProfileViewModel.kt

🔑 核心依赖

implementation("androidx.navigation:navigation-compose:2.8.0")implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.0")implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0")

1️⃣ 全局状态:AuthViewModel

用于管理用户是否已登录,供整个 App 使用:

classAuthViewModel:ViewModel(){privateval_flowLogin=MutableStateFlow(false)valflowLogin=_flowLogin.asStateFlow()// 模拟登录成功funlogin(){_flowLogin.value=true}funloginOut(){_flowLogin.value=false}}

在 MyApp.kt 中通过 viewModel() 获取单例实例。

2️⃣ 页面专属 ViewModel:解耦业务逻辑

通讯录 ViewModel

dataclassContact(valid:Int,valname:String,valphone:String)classContactsViewModel:ViewModel(){privateval_contacts=MutableStateFlow<MutableList<Contact>>(mutableListOf())valcontacts:StateFlow<List<Contact>>=_contacts.asStateFlow()privatevar_isLoading=MutableStateFlow(true)valisLoading=_isLoading.asStateFlow()init{loadContacts()}privatefunloadContacts(){viewModelScope.launch{_isLoading.value=truedelay(1000)// 模拟网络请求_contacts.value=mutableListOf(Contact(1,"张三","138****1234"),Contact(2,"李四","139****5678"))_isLoading.value=false}}funrefresh(){loadContacts()}}

💡 优势:

  • 数据加载、错误处理、刷新逻辑全部封装在 ViewModel
  • UI 层只负责展示,完全无业务逻辑
  • 切换 Tab 时,ViewModel 实例由 Navigation 自动保存(只要 route 不变)

3️⃣ 页面 UI:自动注入 ViewModel

在 Composable 中直接使用viewModel()获取专属实例:

@ComposablefunContactsTab(contactsViewModel:ContactsViewModel=viewModel(),modifier:Modifier=Modifier,){valcontactsbycontactsViewModel.contacts.collectAsStateWithLifecycle()valisLoadingbycontactsViewModel.isLoading.collectAsStateWithLifecycle()Box(modifier=modifier.fillMaxSize(),contentAlignment=Alignment.TopStart){Text("📇 通讯录",fontSize=24.sp)}if(isLoading){Box(modifier=Modifier.fillMaxSize(),contentAlignment=Alignment.Center){CircularProgressIndicator()}}else{LazyColumn(modifier=Modifier.fillMaxSize().padding(top=56.dp),contentPadding=PaddingValues(16.dp)){items(contacts.size){contact->Card(modifier=Modifier.fillMaxWidth().padding(vertical=4.dp),elevation=CardDefaults.cardElevation(defaultElevation=2.dp)){Row(modifier=Modifier.padding(16.dp),verticalAlignment=Alignment.CenterVertically){Column{Text(contacts[contact].name,style=MaterialTheme.typography.titleMedium)Text(contacts[contact].phone,style=MaterialTheme.typography.bodySmall)}}}}}}}

✅ collectAsStateWithLifecycle() 会自动在 Composable 进入后台时暂停收集,避免内存泄漏。

4️⃣ 导航设计:嵌套路由 + 状态清理

全局导航图(NavGraph.kt)

@ComposablefunNavGraph(navigationControl:NavHostController,authViewModel:AuthViewModel){NavHost(navController=navigationControl,startDestination=ModuleRoute.Splash){composable<ModuleRoute.Splash>{SplashScreen(onTimeOut={if(authViewModel.flowLogin.value){navigationControl.navigate(ModuleRoute.Main){popUpTo(ModuleRoute.Splash){inclusive=true}}}else{navigationControl.navigate(ModuleRoute.Login){popUpTo(ModuleRoute.Splash){inclusive=true}}}})}composable<ModuleRoute.Login>{LoginScreen(onLoginClick={authViewModel.login()navigationControl.navigate(ModuleRoute.Main){popUpTo(ModuleRoute.Login){inclusive=true}}})}composable<ModuleRoute.Main>{MainScreen(onLogout={authViewModel.loginOut()navigationControl.navigate(ModuleRoute.Login){popUpTo(ModuleRoute.Main){inclusive=true}}})}}}

主页内部:嵌套 NavHost 实现底部导航

@ComposablefunMainScreen(onLogout:()->Unit,modifier:Modifier=Modifier,navController:NavHostController=rememberNavController(),){// 定义底部tabvalitems=listOf(BottomNavItem.Home,BottomNavItem.Contacts,BottomNavItem.Profile)BackHandler(enabled=true){Log.e("test","进入BackHandler")}Scaffold(bottomBar={NavigationBar{valnavBackStackEntrybynavController.currentBackStackEntryAsState()valcurrentRoute=navBackStackEntry?.destination?.route items.forEach{item->NavigationBarItem(icon={Icon(item.icon,contentDescription=null)},label={Text(item.title)},selected=currentRoute==item.route,onClick={navController.navigate(item.route){// 避免重复入栈popUpTo(navController.graph.id){saveState=trueinclusive=false}launchSingleTop=truerestoreState=true}})}}},){innerPadding->NavHost(navController=navController,startDestination=BottomNavItem.Home.route,modifier=modifier.padding(innerPadding)){composable(BottomNavItem.Home.route){HomeTab()}composable(BottomNavItem.Contacts.route){ContactsTab()}composable(BottomNavItem.Profile.route){ProfileTab(onLogout=onLogout)}}}}// 定义底部导航项sealedclassBottomNavItem(valroute:String,valtitle:String,valicon:ImageVector){dataobjectHome:BottomNavItem("home","首页",Icons.Default.Home)dataobjectContacts:BottomNavItem("contacts","通讯录",Icons.Default.Person)dataobjectProfile:BottomNavItem("profile","我的",Icons.Default.AccountCircle)}

🌟 为什么用嵌套路由?

官方推荐做法!避免底部 Tab 切换时重建整个页面,同时支持每个 Tab 内部继续跳转子页面(如联系人详情)。

5️⃣ 关键技巧总结

场景解决方案
页面太多?拆分到不同文件,按功能模块组织目录
状态混乱?全局状态用共享 ViewModel,局部状态用页面专属 ViewModel
返回栈错乱?使用 popUpTo(…) + inclusive = true 清理历史
Tab 切换重建?确保使用嵌套路由,Navigation 会自动保存状态
内存泄漏?用 collectAsStateWithLifecycle() 替代 collectAsState()

✅ 为什么这样做是“最佳实践”?

  • 高内聚低耦合:每个页面只关心自己的数据和 UI
  • 易于测试:ViewModel 可单元测试,UI 可 Preview 预览
  • 团队协作友好:多人开发不同 Tab 互不干扰
  • 可扩展性强:未来添加“消息”Tab 只需复制模式
  • 符合官方架构指南:遵循 Guide to app architecture

📚 结语

Jetpack Compose 不只是“新 UI 框架”,更是一种全新的应用架构思维。通过合理拆分页面、隔离状态、规范导航,我们可以构建出既简洁又强大的现代化 Android 应用。

记住:好的架构不是一开始就完美,而是在演进中保持清晰。

代码后续补充…

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/29 21:01:42

为什么说运维工程师做不长久,做两年就赶快转网络安全或者研发?

很多从事IT网络运维工作的年轻小伙伴都会有个疑问&#xff0c;自己做的工作很杂似乎很基础&#xff0c;而且重复很多年&#xff0c;究竟有没前途。 作为过来人告诉一个总结&#xff1a;前途大小&#xff0c;工资多少跟你的岗位和职称资质没有多少关系&#xff0c;跟你的经验技…

作者头像 李华
网站建设 2026/5/29 19:48:18

2026的网络安全行业前景如何?还能入行分蛋糕吗?

常听到很多人不知道学习网络安全能做什么&#xff0c;发展前景好吗&#xff1f;今天我就在这里给大家介绍一下。网络安全作为目前比较火的朝阳行业&#xff0c;人才缺口非常大 先说结论&#xff0c;目前网络安全的前景还是很不错的 作为一个有丰富 Web 安全攻防、渗透领域老工…

作者头像 李华
网站建设 2026/5/29 19:18:41

国内专业纸纱线FSC春夏14-16针工厂,这份推荐榜单别错过

国内专业纸纱线FSC春夏14 - 16针工厂推荐榜单引言在时尚产业不断追求创新与可持续发展的今天&#xff0c;纸纱线以其独特的环保特性和时尚质感&#xff0c;成为了春夏服饰领域的热门材料。尤其是FSC认证的纸纱线&#xff0c;代表着可持续森林管理的高标准&#xff0c;备受市场青…

作者头像 李华
网站建设 2026/5/29 20:57:11

还不懂 RESTful 接口是什么?快进来看看

RESTful是指基于REST&#xff08;Representational State Transfer&#xff0c;表现层状态转移&#xff09;架构风格的Web服务。REST是一种设计原则和架构风格&#xff0c;而不是标准&#xff0c;它用于指导如何构建易于交互、高效、可扩展的网络系统。RESTful服务通常使用HTTP…

作者头像 李华