添加依赖项
dependencies {
def lifecycle_version = "2.6.0-alpha03"
// Pass the ViewModel directly to the screen
implementation "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 first
startDestination = ""
) {
/* ... */
}
}
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")
}
@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 created
launchSingleTop = true
}
}
)
}
composable(route = Screen.Favorites.route) {
FavoritesScreen(
navigateBack = {
// Navigate back
navController.popBackStack()
},
navigate = {
navController.navigate(Screen.Cart.route) {
launchSingleTop = true
}
}
)
}
composable(route = Screen.Cart.route) {
CartScreen(
navigateBackToHome = {
// Navigate back to Home screen
navController.popBackStack(
route = Screen.Home.route,
// If this is true, the destination will be removed
inclusive = false
)
}
)
}
}
}
使用 ViewModel
导航
如果 UI 很复杂,您应该使用此方法。
将这些依赖项添加到您的应用程序build.gradle
文件中。
dependencies {
def lifecycle_version = "2.6.0-alpha03"
// Pass the ViewModel directly to the screen
implementation "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): UiEvent
data 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 events
private val _uiEvent: Channel<UiEvent> = Channel()
// Convert the received values into a flow to collect it's data
val uiEvent = _uiEvent.receiveAsFlow()
// Helper function to remove the boilerplate code
private fun sendEvent(event: UiEvent) {
viewModelScope.launch {
_uiEvent.send(event)
}
}
fun onEvent(event: HomeScreenEvents) {
// We have multiple action cases
when (event) {
HomeScreenEvents.OnNavigateToFavorites -> {
// Sends an UiEvent of type Navigation with a route in it
sendEvent(UiEvent.Navigate(route = Screen.Favorites.route))
}
}
}
}
@Composable
fun HomeScreen(
navigate: (UiEvent.Navigate) -> Unit,
// ViewModel is passed by the lifecycle
viewModel: HomeViewModel = viewModel()
) {
LaunchedEffect(key1 = true) {
// Collects the latest event from the channel
viewModel.uiEvent.collectLatest { event ->
when (event) {
is UiEvent.Navigate -> {
navigate(event)
}
else -> Unit
}
}
}
Column {
Text(text = "Home")
Button(
onClick = {
// Sending to the onEvent function an event
viewModel.onEvent(HomeScreenEvents.OnNavigateToFavorites)
}
) {
Text(text = "Navigate to favorites")
}
}
}
// Screen events
sealed interface FavoritesScreenEvents {
object OnNavigateToCart : FavoritesScreenEvents
object OnNavigateBack : FavoritesScreenEvents
}
class FavoritesViewModel : ViewModel() {
// Creating a channel within which we send events
private val _uiEvent: Channel<UiEvent> = Channel()
// Convert the received values into a flow to collect it's data
val uiEvent = _uiEvent.receiveAsFlow()
// Helper function to remove the boilerplate code
private fun sendEvent(event: UiEvent) {
viewModelScope.launch {
_uiEvent.send(event)
}
}
fun onEvent(event: FavoritesScreenEvents) {
when (event) {
FavoritesScreenEvents.OnNavigateBack -> {
// Sends an UiEvent to navigate back
sendEvent(UiEvent.PopBackStack())
}
FavoritesScreenEvents.OnNavigateToCart -> {
// Sends an UiEvent to navigate to cart screen
sendEvent(UiEvent.Navigate(route = Screen.Cart.route))
}
}
}
}
@Composable
fun FavoritesScreen(
// ViewModel is passed by the lifecycle
viewModel: FavoritesViewModel = viewModel(),
navigateBack: () -> Unit,
navigate: (UiEvent.Navigate) -> Unit
) {
LaunchedEffect(key1 = true) {
// Collects the latest event from the channel
viewModel.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 events
private val _uiEvent: Channel<UiEvent> = Channel()
// Convert the received values into a flow to collect it's data
val uiEvent = _uiEvent.receiveAsFlow()
// Helper function to remove the boilerplate code
private fun sendEvent(event: UiEvent) {
viewModelScope.launch {
_uiEvent.send(event)
}
}
fun onEvent(event: CartScreenEvents) {
when (event) {
CartScreenEvents.OnNavigateBackToHome -> {
sendEvent(
UiEvent.PopBackStack(
// Pop to this route
route = Screen.Home.route,
// If this is true, the destination will be popped
inclusive = false
)
)
}
}
}
}
@Composable
fun CartScreen(
// ViewModel is passed by the lifecycle
viewModel: CartViewModel = viewModel(),
navigateBack: (UiEvent.PopBackStack) -> Unit
) {
LaunchedEffect(key1 = true) {
// Collects the latest event from the channel
viewModel.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 screen
if (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