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

一、页面跳转和返回

1.添加 Navigation 依赖

在你的 build.gradle (Module)文件中, 添加 Navigation Compose 依赖。

dependencies {
    implementation ("androidx.navigation:navigation-compose:2.5.3")
}

2.创建跳转页面

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

  • 使用 navController.navigate("routeName") 来触发跳转
  • 使用 NavController.popBackStack() 方法返回到上一页
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController

// 主页面
@Composable
fun HomeScreen(navController: NavController) {
    Column {
        Button(onClick = { navController.navigate("detailScreen") }) {
            Text("跳转到详情页")
        }
        Text("这里是首页")
    }
}

// 详情页面
@Composable
fun DetailScreen(navController: NavController) {
    Column {
        Button(onClick = { navController.popBackStack() }) {
            Text("返回")
        }
        Text("这里是详情页面")
    }
}

3.设置 Navigation Graph

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

  • 使用 NavController 来管理页面的跳转
  • NavHost 是一个 Composable 容器,它包含所有的导航路由。
  • 每个路由由一个唯一的字符串标识,并关联到一个 Composable 函数。
  • startDestination指定从哪个页面开始
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

// 应用的导航设置
@Composable
fun AppNavigation() {
    val navController = rememberNavController() // 创建 NavController

    // 设置 NavHost,管理导航内容
    NavHost(navController = navController, startDestination = "homeScreen") {
        composable("homeScreen") { // 首页路由
            HomeScreen(navController)
        }
        composable("detailScreen") { // 详情页路由
            DetailScreen(navController)
        }
    }
}

这里只是功能演示,直接就用上面这样硬编码了。

延伸示例:

实际开发中,一般将路由名称定义成枚举,避免输入错误,甚至还可以为路由关联页面标题。

import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.randomdt.www.business.home.HomeScreen
import com.randomdt.www.business.modules.protocol.ProtocolScreen
import com.randomdt.www.config.TextConfig
import com.randomdt.www.main.SharedViewModel
import com.randomdt.www.business.guide.GuideScreen

// 应用的导航设置
// 跳转:navController.navigate("detailScreen/Hello, Detail!")
// 返回:navController.popBackStack()
@Composable
fun AppNavigation(viewModel: SharedViewModel, start: RouteList, onComplete: (Boolean) -> Unit = { _ -> Unit }) {
    val navController = rememberNavController() // 创建 NavController

    // 设置 NavHost,管理导航内容
    NavHost(navController = navController, startDestination = start.description) {
        composable(route = RouteList.Home.description) { // 首页路由
            HomeScreen(navController)
        }

        composable(route = RouteList.Guide.description) { // 引导页路由
            GuideScreen(navController, onGuideComplete = onComplete)
        }

        composable(route = RouteList.TermsOfUse.description) { // 用户协议
            ProtocolScreen(navController, titleText = RouteList.TermsOfUse.description, termsText = TextConfig.termsOfUseText())
        }

        composable(route = RouteList.PrivacyPolicy.description) { // 隐私政策
            ProtocolScreen(navController, titleText = RouteList.PrivacyPolicy.description, termsText = TextConfig.privacyPolicyText())
        }
    }
}

// 路由列表
// 可以用来关联一个导航标题名称
enum class RouteList(val description: String) {
    Home("homeScreen"),
    Guide("guideScreen"),
    TermsOfUse("Terms Of Use"),
    PrivacyPolicy("Privacy Policy"),
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        PrefsManager.init(this)

        setContent {
            RandomdtTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MainContent()
                }
            }
        }
    }
}

@Composable
fun MainContent() {
    val viewModel = SharedViewModel()
    val isDidGuideState = remember { mutableStateOf(PrefsManager.get<Boolean>(PrefKey.IS_DID_GUIDE)) }
    if (isDidGuideState.value) {
        AppNavigation(viewModel, start = RouteList.Home)
    } else {
        AppNavigation(viewModel, start = RouteList.Guide, onComplete = { isDidGuideCompleted ->
            isDidGuideState.value = isDidGuideCompleted
        })
    }
}

 

4.在主题中使用 AppNavigation

在你的主要 Composable 函数或者 Activity 中,调用 AppNavigation 函数来启动整个应用的导航系统。

import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    MaterialTheme {
        Surface {
            AppNavigation()
        }
    }
}

二、页面间传值

1.传递数据到新页面

假设我们需要从 HomeScreen 向 DetailScreen 传递一个字符串参数。首先,我们需要在导航图中定义参数。

(1).修改导航图以接受参数

import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.NavType
import androidx.navigation.compose.navArgument

// 应用的导航设置
@Composable
fun AppNavigation() {
    val navController = rememberNavController() // 创建 NavController

    // 设置 NavHost,管理导航内容
    NavHost(navController = navController, startDestination = "homeScreen") {
        composable("homeScreen") { // 首页路由
            HomeScreen(navController)
        }

        composable(
            route = "detailScreen/{message}",
            arguments = listOf(navArgument("message") {
                type = NavType.StringType // 参数的类型
            })
        ) { backStackEntry -> // 详情页路由
            DetailScreen(
                navController = navController,
                message = backStackEntry.arguments?.getString("message") ?: "No message"
            )
        }
    }
}

(2).修改 HomeScreen 以传递参数

@Composable
fun HomeScreen(navController: NavController) {
    Column {
        Button(onClick = { navController.navigate("detailScreen/Hello, Detail!") }) {
            Text("跳转到详情页")
        }
        Text("这里是首页")
    }
}

(3).在 DetailScreen 接收和显示传入的数据

@Composable
fun DetailScreen(navController: NavController, message: String) {
    Column {
        Button(onClick = { navController.popBackStack() }) {
            Text("返回")
        }
        Text("这里是详情页面: $message")
    }
}

2.使用 ViewModel 回传数据

(1).如何创建ViewModel?

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

使用 MutableState:

MutableState 是 Jetpack Compose 中用于状态管理的一种方式,当状态发生变化时,所有使用该状态的 Composable 函数会自动重新绘制。

class ExampleViewModel : ViewModel() {
    val message = mutableStateOf("Initial Message")

    fun updateMessage(newMessage: String) {
        message.value = newMessage
    }
}

@Composable
fun ExampleScreen(viewModel: ExampleViewModel) {
    Text(text = viewModel.message.value)
}
使用 LiveData:

LiveData 同样可以被用于状态管理,在 Jetpack Compose 中,你可以使用 observeAsState() 扩展函数将 LiveData 转换为 Compose 可用的状态。

class ExampleViewModel : ViewModel() {
    private val _message = MutableLiveData("Initial Message")
    val message: LiveData<String> = _message

    fun updateMessage(newMessage: String) {
        _message.value = newMessage
    }
}

@Composable
fun ExampleScreen(viewModel: ExampleViewModel) {
    val message by viewModel.message.observeAsState()
    message?.let {
        Text(text = it)
    }
}
使用 Flow:

Flow 是另一种在 Kotlin 中管理异步数据流的方式。在 Compose 中,你可以使用 collectAsState() 将 Flow 转换为 Compose 可用的状态。

class ExampleViewModel : ViewModel() {
    private val _message = MutableStateFlow("Initial Message")
    val message: StateFlow<String> = _message.asStateFlow()

    fun updateMessage(newMessage: String) {
        _message.value = newMessage
    }
}

@Composable
fun ExampleScreen(viewModel: ExampleViewModel) {
    val message = viewModel.message.collectAsState()
    Text(text = message.value)
}

 (2).定义一个 ViewModel

class MainViewModel : ViewModel() {
    val returnedData = mutableStateOf("")

    fun setReturnData(data: String) {
        returnedData.value = data
    }
}

在实际的应用开发中,使用单一的 MainViewModel 来管理所有页面间的数据传递并不是最佳实践。这种做法可能会导致 ViewModel 过于臃肿和混乱,特别是在大型应用中,这可能会导致维护困难和扩展问题。

 理想的做法是使用多个 ViewModel,每个 ViewModel 管理特定页面或功能模块的状态和逻辑。

  • 页面级 ViewModel:每个页面或每组相关页面可以有自己的 ViewModel。这样,页面间的数据传递可以通过共享的更高级别 ViewModel 来实现,或者通过事件和回调来进行。

  • 共享 ViewModel:对于需要跨多个页面共享数据的情况,可以使用一个共享的 ViewModel。这个 ViewModel 可以被多个页面访问,用于存储和管理共享数据。这通常通过依赖注入框架如 Hilt 或 Dagger 实现。

示例改进:

class HomeViewModel : ViewModel() {
    // Home-specific data and logic
}

class DetailViewModel : ViewModel() {
    // Detail-specific data and logic
    val returnedData = mutableStateOf("")

    fun setReturnData(data: String) {
        returnedData.value = data
    }
}

class SharedViewModel : ViewModel() {
    // Data and logic shared between multiple screens
}

(3).让 AppNavigation 能接收 ViewModel

@Composable
fun AppNavigation(viewModel: MainViewModel) {
    val navController = rememberNavController() // 创建 NavController

    // 设置 NavHost,管理导航内容
    NavHost(navController = navController, startDestination = "homeScreen") {
        composable("homeScreen") { // 首页路由
            HomeScreen(
                navController = navController,
                viewModel = viewModel
            )
        }

        composable(
            route = "detailScreen/{message}",
            arguments = listOf(navArgument("message") {
                type = NavType.StringType // 参数的类型
            })
        ) { backStackEntry -> // 详情页路由
            DetailScreen(
                navController = navController,
                message = backStackEntry.arguments?.getString("message") ?: "No message",
                viewModel = viewModel
            )
        }
    }
}

(4).将 ViewModel 传递给 AppNavigation

@Composable
fun MyApp(viewModel: MainViewModel) {
    MaterialTheme {
        Surface {
            AppNavigation(viewModel)
        }
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val viewModel = MainViewModel()
            MyApp(viewModel)
        }
    }
}

(5).返回页面中使用 ViewModel 来保存数据

@Composable
fun HomeScreen(navController: NavController, viewModel: MainViewModel) {
    Column {
        Button(onClick = { navController.navigate("detailScreen/Hello, Detail!") }) {
            Text("跳转到详情页")
        }
        Text("这里是首页。返回数据=${viewModel.returnedData.value}")
    }
}

// 详情页面
@Composable
fun DetailScreen(navController: NavController, message: String, viewModel: MainViewModel) {
    Column {
        Button(onClick = {
            // 保存数据
            viewModel.setReturnData("Returned from DetailScreen")
            navController.popBackStack()
        }) {
            Text("设置数据并返回")
        }
        Text("这里是详情页面: $message")
    }
}

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

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

  • 单一真相来源(Single Source of Truth): 状态被保留在一个组件内部,所有对状态的修改都通过这个组件进行,这有助于避免不同组件间状态不一致的问题。

  • 可预测性与可维护性: 当状态的更新逻辑集中在一个地方时,更容易追踪状态的变化,调试和维护也更为方便。

  • 解耦: 子组件不需要知道状态是如何被管理的,它们只关心如何显示数据和如何将用户的输入通过回调传递出去。这使得组件更加灵活和独立。

(1).创建共享状态

首先,我们在包含导航逻辑的AppNavigation中创建一个共享状态。这个状态将被HomeScreenDetailScreen共同访问和修改。

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    var sharedData by remember { mutableStateOf("Initial Data") } // 共享状态

    NavHost(navController = navController, startDestination = "homeScreen") {
        composable("homeScreen") {
            HomeScreen(
                navController = navController,
                data = sharedData,
                onNavigateToDetail = { navController.navigate("detailScreen") }
            )
        }
        composable("detailScreen") {
            DetailScreen(
                initialData = sharedData,
                onReturnData = { newData ->
                    sharedData = newData
                    navController.popBackStack()
                }
            )
        }
    }
}

(2).创建 HomeScreen

HomeScreen中,我们展示当前的数据,并提供一个按钮来跳转到DetailScreen

@Composable
fun HomeScreen(navController: NavController, data: String, onNavigateToDetail: () -> Unit) {
    Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Current Data: $data", style = MaterialTheme.typography.headlineMedium)
        Spacer(Modifier.height(20.dp))
        Button(onClick = onNavigateToDetail) {
            Text("Go to DetailScreen")
        }
    }
}

(3).创建 DetailScreen

DetailScreen允许用户修改数据,并通过回调函数将新数据回传给AppNavigation,从而更新共享状态,并返回HomeScreen

@Composable
fun DetailScreen(initialData: String, onReturnData: (String) -> Unit) {
    var text by remember { mutableStateOf(initialData) }

    Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        TextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Enter new data") }
        )
        Spacer(Modifier.height(8.dp))
        Button(
            onClick = { onReturnData(text) }
        ) {
            Text("Update and Return")
        }
    }
}

相关推荐

  1. [Flutter]页面

    2024-04-27 13:24:03       32 阅读
  2. [Android]Jetpack Compose页面

    2024-04-27 13:24:03       131 阅读
  3. uniapp 页面通信上下级页面

    2024-04-27 13:24:03       34 阅读
  4. 关于ReactV18的页面接收

    2024-04-27 13:24:03       27 阅读
  5. react参两种方式

    2024-04-27 13:24:03       59 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-27 13:24:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-27 13:24:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-04-27 13:24:03       82 阅读
  4. Python语言-面向对象

    2024-04-27 13:24:03       91 阅读

热门阅读

  1. wow-slist文件说明

    2024-04-27 13:24:03       41 阅读
  2. android 上传视频

    2024-04-27 13:24:03       32 阅读
  3. 信息的定义及其分类分级

    2024-04-27 13:24:03       43 阅读
  4. 20个 Golang 常见面试问题

    2024-04-27 13:24:03       33 阅读
  5. 关于深度学习图像数据集的主要问题和考虑事项

    2024-04-27 13:24:03       34 阅读
  6. 并行计算+Linux process

    2024-04-27 13:24:03       34 阅读
  7. Docker 容器创建与使用问题汇总

    2024-04-27 13:24:03       34 阅读
  8. git分支更新

    2024-04-27 13:24:03       29 阅读
  9. MATLAB初学者入门(22)—— 哈希算法

    2024-04-27 13:24:03       37 阅读
  10. 2024版GB4806.15直接接触粘合剂第三方检测机构

    2024-04-27 13:24:03       33 阅读
  11. WPF之Label

    2024-04-27 13:24:03       33 阅读