一、页面跳转和返回
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.Composableclass 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
中的数据是以响应式的方式管理的(例如使用 MutableState
、LiveData
或 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> = _messagefun 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 logicval 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
中创建一个共享状态。这个状态将被HomeScreen
和DetailScreen
共同访问和修改。
@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 = newDatanavController.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")}}
}