> 文章列表 > Jetpack Compose中使用Navigation导航的两种方式

Jetpack Compose中使用Navigation导航的两种方式

Jetpack Compose中使用Navigation导航的两种方式

Jetpack Compose中使用Navigation导航的两种方式

Jetpack Compose中使用Navigation导航的两种方式
这里介绍两种方式

  1. 直接从屏幕导航
  2. 使用 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