news 2026/8/8 18:07:44

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/8/7 23:53:29

CANN Samples(十九):特色场景:机器人 AI 绘画 手写识别等

1. 当AI不止于识别&#xff1a;探索CANN的趣味应用 在我们之前的文章中&#xff0c;我们聊了许多关于图像分类、目标检测的话题&#xff0c;这些都是AI在“看懂”世界方面的应用。但AI的魅力远不止于此。当它与物理世界互动&#xff0c;会诞生出机器人&#xff1b;当它被赋予创…

作者头像 李华
网站建设 2026/8/7 21:53:53

毕设开源 深度学习YOLO交通路面缺陷检测系统(源码+论文)

文章目录0 前言1 项目运行效果2 课题背景2.1. 道路基础设施发展现状2.2. 路面缺陷检测技术演进历程2.2.1 传统检测方法2.2.2 机械化检测阶段2.3. 计算机视觉技术突破2.3.1 传统图像处理技术2.3.2 深度学习革命2.4. 路面缺陷检测的特殊性挑战2.4.1 数据特性2.4.2 技术难点2.5. 本…

作者头像 李华
网站建设 2026/8/8 7:19:27

【URP】Unity[后处理]色彩调整ColorAdjustments

4.X时代的色彩校正曲线和3D查找纹理技术&#xff0c;早期通过手动调节RGB通道曲线实现基础调色功能&#xff0c;而在URP中已整合为标准化体积框架下的模块化组件。底层原理Unity URP中的Color Adjustments后处理效果基于渲染管线中的片段着色器操作&#xff0c;其核心原理是通过…

作者头像 李华
网站建设 2026/8/8 3:42:55

Flutter混合开发与WebView集成实战

&#x1f517; 实战项目&#xff1a;openharmonycrossplatform.csdn.net/content &#x1f4d6; 目录 &#x1f310; WebView集成 &#x1f517; 混合通信 &#x1f4f1; 原生嵌入 &#x1f3af; 性能优化 &#x1f310; 一、WebView深度集成 1.1 WebView基础封装 dart …

作者头像 李华
网站建设 2026/8/7 15:25:03

压缩空气储能和释能阶段模型,附相关文档文献。 建立了压缩空气储能系统中的压缩机、换热器、储气罐...

压缩空气储能和释能阶段模型&#xff0c;附相关文档文献。 建立了压缩空气储能系统中的压缩机、换热器、储气罐、透平、热水罐等设备的数学模型、 并在 Simulink仿真平台上、 按模块化建模方式完成了系统相关程序编写和仿真模型建立、 包含储能和释能两个阶段的模型。压缩空气储…

作者头像 李华
网站建设 2026/8/7 22:41:00

Profinet转Modbus TCP工业数据采集网关:实现1200PLC 与打标卡数据实时传输

一、项目背景与需求某机械加工产线需通过西门子 S7-1200 PLC 实现对工业打标卡的自动化控制&#xff0c;完成零部件序列号、批次码的激光打标。打标卡仅支持 Modbus TCP 协议&#xff0c;而产线控制层采用 ProfiNet 总线架构&#xff0c;需解决异协议通讯兼容问题&#xff0c;同…

作者头像 李华