[Android]Jetpack Compose页面跳转和传值

打印 上一主题 下一主题

主题 833|帖子 833|积分 2499

一、页面跳转和返回

1.添加 Navigation 依赖

在你的 build.gradle (Module)文件中, 添加 Navigation Compose 依赖。
  1. dependencies {
  2.     implementation ("androidx.navigation:navigation-compose:2.5.3")
  3. }
复制代码
2.创建跳转页面

接下来,创建两个简单的 Composable 函数,分别表示两个页面。


  • 使用 navController.navigate("routeName") 来触发跳转
  • 使用 NavController.popBackStack() 方法返回到上一页
  1. import androidx.compose.material.Button
  2. import androidx.compose.material.Text
  3. import androidx.compose.runtime.Composable
  4. import androidx.compose.ui.platform.LocalContext
  5. import androidx.navigation.NavController
  6. // 主页面
  7. @Composable
  8. fun HomeScreen(navController: NavController) {
  9.     Column {
  10.         Button(onClick = { navController.navigate("detailScreen") }) {
  11.             Text("跳转到详情页")
  12.         }
  13.         Text("这里是首页")
  14.     }
  15. }
  16. // 详情页面
  17. @Composable
  18. fun DetailScreen(navController: NavController) {
  19.     Column {
  20.         Button(onClick = { navController.popBackStack() }) {
  21.             Text("返回")
  22.         }
  23.         Text("这里是详情页面")
  24.     }
  25. }
复制代码
3.设置 Navigation Graph

界说一个 Navigation Graph 来管理你的应用导航。这包罗界说所有的导航路由和相关的 Composable 函数。


  • 使用 NavController 来管理页面的跳转
  • NavHost 是一个 Composable 容器,它包含所有的导航路由。
  • 每个路由由一个唯一的字符串标识,并关联到一个 Composable 函数。
  • startDestination指定从哪个页面开始
  1. import androidx.compose.runtime.Composable
  2. import androidx.navigation.compose.NavHost
  3. import androidx.navigation.compose.composable
  4. import androidx.navigation.compose.rememberNavController
  5. // 应用的导航设置
  6. @Composable
  7. fun AppNavigation() {
  8.     val navController = rememberNavController() // 创建 NavController
  9.     // 设置 NavHost,管理导航内容
  10.     NavHost(navController = navController, startDestination = "homeScreen") {
  11.         composable("homeScreen") { // 首页路由
  12.             HomeScreen(navController)
  13.         }
  14.         composable("detailScreen") { // 详情页路由
  15.             DetailScreen(navController)
  16.         }
  17.     }
  18. }
复制代码
这里只是功能演示,直接就用上面这样硬编码了。

延伸示例:
现实开发中,一般将路由名称界说成罗列,避免输入错误,甚至还可以为路由关联页面标题。
  1. import androidx.compose.runtime.Composable
  2. import androidx.navigation.compose.NavHost
  3. import androidx.navigation.compose.composable
  4. import androidx.navigation.compose.rememberNavController
  5. import com.randomdt.www.business.home.HomeScreen
  6. import com.randomdt.www.business.modules.protocol.ProtocolScreen
  7. import com.randomdt.www.config.TextConfig
  8. import com.randomdt.www.main.SharedViewModel
  9. import com.randomdt.www.business.guide.GuideScreen
  10. // 应用的导航设置
  11. // 跳转:navController.navigate("detailScreen/Hello, Detail!")
  12. // 返回:navController.popBackStack()
  13. @Composable
  14. fun AppNavigation(viewModel: SharedViewModel, start: RouteList, onComplete: (Boolean) -> Unit = { _ -> Unit }) {
  15.     val navController = rememberNavController() // 创建 NavController
  16.     // 设置 NavHost,管理导航内容
  17.     NavHost(navController = navController, startDestination = start.description) {
  18.         composable(route = RouteList.Home.description) { // 首页路由
  19.             HomeScreen(navController)
  20.         }
  21.         composable(route = RouteList.Guide.description) { // 引导页路由
  22.             GuideScreen(navController, onGuideComplete = onComplete)
  23.         }
  24.         composable(route = RouteList.TermsOfUse.description) { // 用户协议
  25.             ProtocolScreen(navController, titleText = RouteList.TermsOfUse.description, termsText = TextConfig.termsOfUseText())
  26.         }
  27.         composable(route = RouteList.PrivacyPolicy.description) { // 隐私政策
  28.             ProtocolScreen(navController, titleText = RouteList.PrivacyPolicy.description, termsText = TextConfig.privacyPolicyText())
  29.         }
  30.     }
  31. }
  32. // 路由列表
  33. // 可以用来关联一个导航标题名称
  34. enum class RouteList(val description: String) {
  35.     Home("homeScreen"),
  36.     Guide("guideScreen"),
  37.     TermsOfUse("Terms Of Use"),
  38.     PrivacyPolicy("Privacy Policy"),
  39. }
复制代码
  1. class MainActivity : ComponentActivity() {
  2.     override fun onCreate(savedInstanceState: Bundle?) {
  3.         super.onCreate(savedInstanceState)
  4.         PrefsManager.init(this)
  5.         setContent {
  6.             RandomdtTheme {
  7.                 Surface(
  8.                     modifier = Modifier.fillMaxSize(),
  9.                     color = MaterialTheme.colorScheme.background
  10.                 ) {
  11.                     MainContent()
  12.                 }
  13.             }
  14.         }
  15.     }
  16. }
  17. @Composable
  18. fun MainContent() {
  19.     val viewModel = SharedViewModel()
  20.     val isDidGuideState = remember { mutableStateOf(PrefsManager.get<Boolean>(PrefKey.IS_DID_GUIDE)) }
  21.     if (isDidGuideState.value) {
  22.         AppNavigation(viewModel, start = RouteList.Home)
  23.     } else {
  24.         AppNavigation(viewModel, start = RouteList.Guide, onComplete = { isDidGuideCompleted ->
  25.             isDidGuideState.value = isDidGuideCompleted
  26.         })
  27.     }
  28. }
复制代码
 
4.在主题中使用 AppNavigation

在你的主要 Composable 函数或者 Activity 中,调用 AppNavigation 函数来启动整个应用的导航系统。
  1. import androidx.activity.ComponentActivity
  2. import androidx.activity.compose.setContent
  3. import androidx.compose.material.MaterialTheme
  4. import androidx.compose.material.Surface
  5. import androidx.compose.runtime.Composable
  6. class MainActivity : ComponentActivity() {
  7.     override fun onCreate(savedInstanceState: Bundle?) {
  8.         super.onCreate(savedInstanceState)
  9.         setContent {
  10.             MyApp()
  11.         }
  12.     }
  13. }
  14. @Composable
  15. fun MyApp() {
  16.     MaterialTheme {
  17.         Surface {
  18.             AppNavigation()
  19.         }
  20.     }
  21. }
复制代码

二、页面间传值

1.传递数据到新页面

假设我们需要从 HomeScreen 向 DetailScreen 传递一个字符串参数。首先,我们需要在导航图中界说参数。
(1).修改导航图以接受参数

  1. import androidx.navigation.compose.NavHost
  2. import androidx.navigation.compose.composable
  3. import androidx.navigation.compose.rememberNavController
  4. import androidx.navigation.NavType
  5. import androidx.navigation.compose.navArgument
  6. // 应用的导航设置
  7. @Composable
  8. fun AppNavigation() {
  9.     val navController = rememberNavController() // 创建 NavController
  10.     // 设置 NavHost,管理导航内容
  11.     NavHost(navController = navController, startDestination = "homeScreen") {
  12.         composable("homeScreen") { // 首页路由
  13.             HomeScreen(navController)
  14.         }
  15.         composable(
  16.             route = "detailScreen/{message}",
  17.             arguments = listOf(navArgument("message") {
  18.                 type = NavType.StringType // 参数的类型
  19.             })
  20.         ) { backStackEntry -> // 详情页路由
  21.             DetailScreen(
  22.                 navController = navController,
  23.                 message = backStackEntry.arguments?.getString("message") ?: "No message"
  24.             )
  25.         }
  26.     }
  27. }
复制代码
(2).修改 HomeScreen 以传递参数

  1. @Composable
  2. fun HomeScreen(navController: NavController) {
  3.     Column {
  4.         Button(onClick = { navController.navigate("detailScreen/Hello, Detail!") }) {
  5.             Text("跳转到详情页")
  6.         }
  7.         Text("这里是首页")
  8.     }
  9. }
复制代码
(3).在 DetailScreen 接收和显示传入的数据

  1. @Composable
  2. fun DetailScreen(navController: NavController, message: String) {
  3.     Column {
  4.         Button(onClick = { navController.popBackStack() }) {
  5.             Text("返回")
  6.         }
  7.         Text("这里是详情页面: $message")
  8.     }
  9. }
复制代码

2.使用 ViewModel 回传数据

(1).怎样创建ViewModel?

在使用 ViewModel 和 Jetpack Compose 联合的场景中,如果 ViewModel 中的数据是以相应式的方式管理的(例如使用 MutableState、LiveData 或 Flow),那么当数据更新时,与这些数据相关的 Compose UI 组件将会自动重新渲染以反映新的数据状态。
使用 MutableState:

MutableState 是 Jetpack Compose 中用于状态管理的一种方式,当状态发生变化时,所有使用该状态的 Composable 函数会自动重新绘制。
  1. class ExampleViewModel : ViewModel() {
  2.     val message = mutableStateOf("Initial Message")
  3.     fun updateMessage(newMessage: String) {
  4.         message.value = newMessage
  5.     }
  6. }
  7. @Composable
  8. fun ExampleScreen(viewModel: ExampleViewModel) {
  9.     Text(text = viewModel.message.value)
  10. }
复制代码
使用 LiveData:

LiveData 同样可以被用于状态管理,在 Jetpack Compose 中,你可以使用 observeAsState() 扩展函数将 LiveData 转换为 Compose 可用的状态。
  1. class ExampleViewModel : ViewModel() {
  2.     private val _message = MutableLiveData("Initial Message")
  3.     val message: LiveData<String> = _message
  4.     fun updateMessage(newMessage: String) {
  5.         _message.value = newMessage
  6.     }
  7. }
  8. @Composable
  9. fun ExampleScreen(viewModel: ExampleViewModel) {
  10.     val message by viewModel.message.observeAsState()
  11.     message?.let {
  12.         Text(text = it)
  13.     }
  14. }
复制代码
使用 Flow:

Flow 是另一种在 Kotlin 中管理异步数据流的方式。在 Compose 中,你可以使用 collectAsState() 将 Flow 转换为 Compose 可用的状态。
  1. class ExampleViewModel : ViewModel() {
  2.     private val _message = MutableStateFlow("Initial Message")
  3.     val message: StateFlow<String> = _message.asStateFlow()
  4.     fun updateMessage(newMessage: String) {
  5.         _message.value = newMessage
  6.     }
  7. }
  8. @Composable
  9. fun ExampleScreen(viewModel: ExampleViewModel) {
  10.     val message = viewModel.message.collectAsState()
  11.     Text(text = message.value)
  12. }
复制代码
 (2).界说一个 ViewModel

  1. class MainViewModel : ViewModel() {
  2.     val returnedData = mutableStateOf("")
  3.     fun setReturnData(data: String) {
  4.         returnedData.value = data
  5.     }
  6. }
复制代码
在现实的应用开发中,使用单一的 MainViewModel 来管理所有页面间的数据传递并不是最佳实践。这种做法可能会导致 ViewModel 过于臃肿和混乱,特别是在大型应用中,这可能会导致维护困难和扩展问题。
 理想的做法是使用多个 ViewModel,每个 ViewModel 管理特定页面或功能模块的状态和逻辑。


  • 页面级 ViewModel:每个页面或每组相关页面可以有自己的 ViewModel。这样,页面间的数据传递可以通过共享的更高级别 ViewModel 来实现,或者通过事件和回调来举行。
  • 共享 ViewModel:对于需要跨多个页面共享数据的情况,可以使用一个共享的 ViewModel。这个 ViewModel 可以被多个页面访问,用于存储和管理共享数据。这通常通过依赖注入框架如 Hilt 或 Dagger 实现。
示例改进:
  1. class HomeViewModel : ViewModel() {
  2.     // Home-specific data and logic
  3. }
  4. class DetailViewModel : ViewModel() {
  5.     // Detail-specific data and logic
  6.     val returnedData = mutableStateOf("")
  7.     fun setReturnData(data: String) {
  8.         returnedData.value = data
  9.     }
  10. }
  11. class SharedViewModel : ViewModel() {
  12.     // Data and logic shared between multiple screens
  13. }
复制代码
(3).让 AppNavigation 能接收 ViewModel

  1. @Composable
  2. fun AppNavigation(viewModel: MainViewModel) {
  3.     val navController = rememberNavController() // 创建 NavController
  4.     // 设置 NavHost,管理导航内容
  5.     NavHost(navController = navController, startDestination = "homeScreen") {
  6.         composable("homeScreen") { // 首页路由
  7.             HomeScreen(
  8.                 navController = navController,
  9.                 viewModel = viewModel
  10.             )
  11.         }
  12.         composable(
  13.             route = "detailScreen/{message}",
  14.             arguments = listOf(navArgument("message") {
  15.                 type = NavType.StringType // 参数的类型
  16.             })
  17.         ) { backStackEntry -> // 详情页路由
  18.             DetailScreen(
  19.                 navController = navController,
  20.                 message = backStackEntry.arguments?.getString("message") ?: "No message",
  21.                 viewModel = viewModel
  22.             )
  23.         }
  24.     }
  25. }
复制代码
(4).将 ViewModel 传递给 AppNavigation

  1. @Composable
  2. fun MyApp(viewModel: MainViewModel) {
  3.     MaterialTheme {
  4.         Surface {
  5.             AppNavigation(viewModel)
  6.         }
  7.     }
  8. }
  9. class MainActivity : ComponentActivity() {
  10.     override fun onCreate(savedInstanceState: Bundle?) {
  11.         super.onCreate(savedInstanceState)
  12.         setContent {
  13.             val viewModel = MainViewModel()
  14.             MyApp(viewModel)
  15.         }
  16.     }
  17. }
复制代码
(5).返回页面中使用 ViewModel 来生存数据

  1. @Composable
  2. fun HomeScreen(navController: NavController, viewModel: MainViewModel) {
  3.     Column {
  4.         Button(onClick = { navController.navigate("detailScreen/Hello, Detail!") }) {
  5.             Text("跳转到详情页")
  6.         }
  7.         Text("这里是首页。返回数据=${viewModel.returnedData.value}")
  8.     }
  9. }
  10. // 详情页面
  11. @Composable
  12. fun DetailScreen(navController: NavController, message: String, viewModel: MainViewModel) {
  13.     Column {
  14.         Button(onClick = {
  15.             // 保存数据
  16.             viewModel.setReturnData("Returned from DetailScreen")
  17.             navController.popBackStack()
  18.         }) {
  19.             Text("设置数据并返回")
  20.         }
  21.         Text("这里是详情页面: $message")
  22.     }
  23. }
复制代码


3.使用事件回调和状态提升回传数据

在 Jetpack Compose 中使用状态提升(State Hoisting)的模式时,通常是将状态管理的责任留给上层组件(通常是状态的拥有者),而不是让各个子组件直接修改共享状态。这种模式有几个关键的优点:


  • 单一本相来源(Single Source of Truth): 状态被保存在一个组件内部,所有对状态的修改都通过这个组件举行,这有助于避免差别组件间状态不一致的问题。
  • 可猜测性与可维护性: 当状态的更新逻辑会合在一个地方时,更容易追踪状态的变化,调试和维护也更为方便。
  • 解耦: 子组件不需要知道状态是怎样被管理的,它们只关心怎样显示数据和怎样将用户的输入通过回调传递出去。这使得组件更加机动和独立。
(1).创建共享状态

首先,我们在包含导航逻辑的AppNavigation中创建一个共享状态。这个状态将被HomeScreen和DetailScreen共同访问和修改。
  1. @Composable
  2. fun AppNavigation() {
  3.     val navController = rememberNavController()
  4.     var sharedData by remember { mutableStateOf("Initial Data") } // 共享状态
  5.     NavHost(navController = navController, startDestination = "homeScreen") {
  6.         composable("homeScreen") {
  7.             HomeScreen(
  8.                 navController = navController,
  9.                 data = sharedData,
  10.                 onNavigateToDetail = { navController.navigate("detailScreen") }
  11.             )
  12.         }
  13.         composable("detailScreen") {
  14.             DetailScreen(
  15.                 initialData = sharedData,
  16.                 onReturnData = { newData ->
  17.                     sharedData = newData
  18.                     navController.popBackStack()
  19.                 }
  20.             )
  21.         }
  22.     }
  23. }
复制代码
(2).创建 HomeScreen

在HomeScreen中,我们展示当前的数据,并提供一个按钮来跳转到DetailScreen。
  1. @Composable
  2. fun HomeScreen(navController: NavController, data: String, onNavigateToDetail: () -> Unit) {
  3.     Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
  4.         Text(text = "Current Data: $data", style = MaterialTheme.typography.headlineMedium)
  5.         Spacer(Modifier.height(20.dp))
  6.         Button(onClick = onNavigateToDetail) {
  7.             Text("Go to DetailScreen")
  8.         }
  9.     }
  10. }
复制代码
(3).创建 DetailScreen

DetailScreen允许用户修改数据,并通过回调函数将新数据回传给AppNavigation,从而更新共享状态,并返回HomeScreen。
  1. @Composable
  2. fun DetailScreen(initialData: String, onReturnData: (String) -> Unit) {
  3.     var text by remember { mutableStateOf(initialData) }
  4.     Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
  5.         TextField(
  6.             value = text,
  7.             onValueChange = { text = it },
  8.             label = { Text("Enter new data") }
  9.         )
  10.         Spacer(Modifier.height(8.dp))
  11.         Button(
  12.             onClick = { onReturnData(text) }
  13.         ) {
  14.             Text("Update and Return")
  15.         }
  16.     }
  17. }
复制代码





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表