Jetpack Compose中使用Navigation导航的两种方式
Jetpack Compose中使用Navigation导航的两种方式
这里介绍两种方式
- 直接从屏幕导航
- 使用 ViewModel 导航
直接从Screen导航
设置
添加依赖项
dependencies { def lifecycle_version = "2.6.0-alpha03"// Pass the ViewModel directly to the screenimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
}
设置导航
让我们创建一个Navigation.kt
文件 ,我们将在其中设置导航。
@Composable
fun Navigation() {val navController = rememberNavController()NavHost(navController = navController,// We need to create the routes firststartDestination = "") {/* ... */}
}
NavController
— 允许我们导航到其他屏幕并跟踪可组合项的返回堆栈。
NavHost
— 将NavController
与导航图链接,该导航图指定您应该能够在屏幕之间导航的可组合目的地。
创建路由
创建路由的方法有多种,但我会向您展示我喜欢的一种。首先,我创建了一个名为Screen.kt
的文件,并将它们存储在一个密封类中。
sealed class Screen(val route: String) {object Home : Screen(route = "home")object Favorites : Screen(route = "favorites")object Cart : Screen(route = "cart")
}
将屏幕添加到 NavHost
@Composable
fun Navigation() {val navController = rememberNavController()NavHost(navController = navController,startDestination = Screen.Home.route) {composable(route = Screen.Home.route) {/* ... */}composable(route = Screen.Favorites.route) {/* ... */}composable(route = Screen.Cart.route) {/* ... */}}
}
创建屏幕
@Composable
fun HomeScreen(navigate: () -> Unit
) {Column {Text(text = "Home")Button(onClick = navigate) {Text(text = "Navigate to favorites")}}
}@Composable
fun FavoritesScreen(navigateBack: () -> Unit,navigate: () -> Unit
) {Column {Text(text = "Favorites")Button(onClick = navigateBack) {Text(text = "Navigate back")}Button(onClick = navigate) {Text(text = "Navigate to cart")}}
}@Composable
fun CartScreen(navigateBackToHome: () -> Unit) {Column {Text(text = "Cart")Button(onClick = navigateBackToHome) {Text(text = "Navigate back to home")}}
}
建议不要共享NavController
直达屏幕,您应该只调用navigate()
作为回调的一部分而不是可组合项本身,以避免调用navigate()
每次重组。
@Composable
fun Navigation() {val navController = rememberNavController()NavHost(navController = navController,startDestination = Screen.Home.route) {composable(route = Screen.Home.route) {HomeScreen(navigate = {navController.navigate(Screen.Favorites.route) {// If it is true, multiple copies won't be createdlaunchSingleTop = true}})}composable(route = Screen.Favorites.route) {FavoritesScreen(navigateBack = {// Navigate backnavController.popBackStack()},navigate = {navController.navigate(Screen.Cart.route) {launchSingleTop = true}})}composable(route = Screen.Cart.route) {CartScreen(navigateBackToHome = {// Navigate back to Home screennavController.popBackStack(route = Screen.Home.route,// If this is true, the destination will be removedinclusive = false)})}}
}
使用 ViewModel 导航
使用 ViewModel
导航
如果 UI 很复杂,您应该使用此方法。
设置
将这些依赖项添加到您的应用程序build.gradle
文件中。
dependencies { def lifecycle_version = "2.6.0-alpha03"// Pass the ViewModel directly to the screenimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
}
创建一个名为 UiEvent.kt
的密封接口,这是我们的应用程序事件。
sealed interface UiEvent {data class Navigate(val route: String): UiEventdata class PopBackStack(val route: String? = null, val inclusive: Boolean = false): UiEvent
}
让我们分析一下我们的Screen、ViewModel 和Event
。
// Screen events
sealed interface HomeScreenEvents {object OnNavigateToFavorites : HomeScreenEvents
}class HomeViewModel : ViewModel() {// Creating a channel within which we send eventsprivate val _uiEvent: Channel<UiEvent> = Channel()// Convert the received values into a flow to collect it's dataval uiEvent = _uiEvent.receiveAsFlow()// Helper function to remove the boilerplate codeprivate fun sendEvent(event: UiEvent) {viewModelScope.launch {_uiEvent.send(event)}}fun onEvent(event: HomeScreenEvents) {// We have multiple action caseswhen (event) {HomeScreenEvents.OnNavigateToFavorites -> {// Sends an UiEvent of type Navigation with a route in itsendEvent(UiEvent.Navigate(route = Screen.Favorites.route))}}}
}@Composable
fun HomeScreen(navigate: (UiEvent.Navigate) -> Unit,// ViewModel is passed by the lifecycleviewModel: HomeViewModel = viewModel()
) {LaunchedEffect(key1 = true) {// Collects the latest event from the channelviewModel.uiEvent.collectLatest { event ->when (event) {is UiEvent.Navigate -> {navigate(event)}else -> Unit}}}Column {Text(text = "Home")Button(onClick = {// Sending to the onEvent function an eventviewModel.onEvent(HomeScreenEvents.OnNavigateToFavorites)}) {Text(text = "Navigate to favorites")}}
}
// Screen events
sealed interface FavoritesScreenEvents {object OnNavigateToCart : FavoritesScreenEventsobject OnNavigateBack : FavoritesScreenEvents
}class FavoritesViewModel : ViewModel() {// Creating a channel within which we send eventsprivate val _uiEvent: Channel<UiEvent> = Channel()// Convert the received values into a flow to collect it's dataval uiEvent = _uiEvent.receiveAsFlow()// Helper function to remove the boilerplate codeprivate fun sendEvent(event: UiEvent) {viewModelScope.launch {_uiEvent.send(event)}}fun onEvent(event: FavoritesScreenEvents) {when (event) {FavoritesScreenEvents.OnNavigateBack -> {// Sends an UiEvent to navigate backsendEvent(UiEvent.PopBackStack())}FavoritesScreenEvents.OnNavigateToCart -> {// Sends an UiEvent to navigate to cart screensendEvent(UiEvent.Navigate(route = Screen.Cart.route))}}}
}@Composable
fun FavoritesScreen(// ViewModel is passed by the lifecycleviewModel: FavoritesViewModel = viewModel(),navigateBack: () -> Unit,navigate: (UiEvent.Navigate) -> Unit
) {LaunchedEffect(key1 = true) {// Collects the latest event from the channelviewModel.uiEvent.collectLatest { event ->when (event) {is UiEvent.Navigate -> {navigate(event)}is UiEvent.PopBackStack -> {navigateBack()}}}}Column {Text(text = "Favorites")Button(onClick = {viewModel.onEvent(FavoritesScreenEvents.OnNavigateBack)}) {Text(text = "Navigate back")}Button(onClick = {viewModel.onEvent(FavoritesScreenEvents.OnNavigateToCart)}) {Text(text = "Navigate to cart")}}
}
// Screen events
sealed interface CartScreenEvents {object OnNavigateBackToHome : CartScreenEvents
}class CartViewModel : ViewModel() {// Creating a channel within which we send eventsprivate val _uiEvent: Channel<UiEvent> = Channel()// Convert the received values into a flow to collect it's dataval uiEvent = _uiEvent.receiveAsFlow()// Helper function to remove the boilerplate codeprivate fun sendEvent(event: UiEvent) {viewModelScope.launch {_uiEvent.send(event)}}fun onEvent(event: CartScreenEvents) {when (event) {CartScreenEvents.OnNavigateBackToHome -> {sendEvent(UiEvent.PopBackStack(// Pop to this routeroute = Screen.Home.route,// If this is true, the destination will be poppedinclusive = false))}}}
}@Composable
fun CartScreen(// ViewModel is passed by the lifecycleviewModel: CartViewModel = viewModel(),navigateBack: (UiEvent.PopBackStack) -> Unit
) {LaunchedEffect(key1 = true) {// Collects the latest event from the channelviewModel.uiEvent.collectLatest { event ->when (event) {is UiEvent.PopBackStack -> {navigateBack(event)}else -> Unit}}}Column {Text(text = "Cart")Button(onClick = {viewModel.onEvent(CartScreenEvents.OnNavigateBackToHome)}) {Text(text = "Navigate back to home")}}
}
LaunchedEffect
是一个 Side-effect
。
@Composable
fun Navigation() {val navController = rememberNavController()NavHost(navController = navController,startDestination = Screen.Home.route) {composable(route = Screen.Home.route) {HomeScreen(navigate = { destination ->navController.navigate(destination.route) {launchSingleTop = true}})}composable(route = Screen.Favorites.route) {FavoritesScreen(navigateBack = {navController.popBackStack()},navigate = { destination ->navController.navigate(destination.route) {launchSingleTop = true}})}composable(route = Screen.Cart.route) {CartScreen(navigateBack = { destination ->// In a bigger project you can either// pop up to a specific screen or to the previous screenif (destination.route == null) {navController.popBackStack()} else {navController.popBackStack(route = destination.route,inclusive = destination.inclusive)}})}}
}
参考
https://medium.com/@daniel.atitienei/navigation-in-jetpack-compose-88a92c40e98b#055f