做主页导航时会用到底部导航栏,Jetpack Compose提供了基础槽位的布局Scaffold,使用Scaffold可以构建底部导航栏,例如:

  1. @Composable
  2. fun Greeting(vm: VM) {
  3. val list = listOf("One", "Two", "Three")
  4. var selectedItem = remember {
  5. mutableStateOf(0)
  6. }
  7. val navController = rememberNavController()
  8. Scaffold(bottomBar = {
  9. state.takeIf { it.value }?.let {
  10. BottomNavigation {
  11. list.forEachIndexed { index, label ->
  12. BottomNavigationItem(
  13. label = { Text(text = label) },
  14. selected = index == selectedItem.value,
  15. onClick = { selectedItem.value = index },
  16. icon = {
  17. Icon(
  18. painter = painterResource(id = R.drawable.ic_launcher_foreground),
  19. contentDescription = null
  20. )
  21. })
  22. }
  23. }
  24. }
  25. }) {
  26. NavHost(navController = navController, startDestination = "one") {
  27. composable(route = "one") { PageList(navController, vm) }
  28. composable(route = "detail") { PageDetail(vm) }
  29. }
  30. }
  31. }

这是一个最简单的Scaffold,其主页时PageList,显示一列数字,点击数字后会跳转到PageDetail页面。

但是有个很大的问题,就是在跳转到PageDetail页面之后,BottomNavigation并没有随之消失,于是乎出现了这样一个奇怪的现象:

为了解决这个问题,可以采用State去控制BottomNavigation的可见性,并将其保存在ViewModel中。
具体做法是:
1.在ViewModel中创建一个包含Boolean值的LiveData变量state。当state为true时绘制BottomNavigation,为false时不绘制
2.在包含Scaffold页面中监听state,并控制BottomNavigation的可见性。
3.在PageList(也就是Scaffold导航的主页)进入时设置state为true、退出时设置state为false

  1. // ViewModel
  2. class VM: ViewModel() {
  3. private val _state: MutableLiveData<Boolean> = MutableLiveData(true)
  4. val state: LiveData<Boolean> get() = _state
  5. fun setState(status: Boolean) {
  6. _state.postValue(status)
  7. }
  8. }
  9. // MainPage
  10. @Compose MainPage(vm: VM) {
  11. LaunchedEffect(key1 = true) {
  12. vm.setState(true)
  13. }
  14. DisposableEffect(key1 = true) {
  15. onDispose {
  16. vm.setState(false)
  17. }
  18. }
  19. }
  20. // page contains Scaffold
  21. @Composable
  22. fun Greeting(vm: VM) {
  23. // State of BottomNavigation`s visibility
  24. val state = remember { mutableStateOf<Boolean>(true) }
  25. // read the BottomNavigation`s visibility from ViewModel and send to State
  26. vm.state.observeAsState().value?.let { state.value = it }
  27. Scaffold(bottomBar = {
  28. // show / hide BottomNavigation controlled by State
  29. state.takeIf { it.value }?.let {
  30. BottomNavigation {
  31. list.forEachIndexed { index, label ->
  32. BottomNavigationItem(
  33. label = { Text(text = label) },
  34. selected = index == selectedItem.value,
  35. onClick = { selectedItem.value = index },
  36. icon = {
  37. Icon(
  38. painter = painterResource(id = R.drawable.ic_launcher_foreground),
  39. contentDescription = null
  40. )
  41. })
  42. }
  43. }
  44. }
  45. }) {
  46. NavHost(navController = navController, startDestination = "one") {
  47. composable(route = "one") { PageList(navController, vm) }
  48. composable(route = "detail") { PageDetail(vm) }
  49. }
  50. }
  51. }

这种做法的好处是简单,侵入性低,无需修改系统api也无需自定义view。缺点就是麻烦,需要在导航中的每个主页都进行设置。


我在StackOverflow上提问时有人回答了另一个办法。这个办法是给每个屏幕添加标志位,来区分是否是导航的主页,之后再创建BottomNavigation时进行判断。贴一下:

You need to specify which screens you want to show and which screens you dont want; Otherwise it will show to all the screens inside Scaffold’s body (which you have bottomBar). The code below was from my app.

Create a state which observes any destination changes on the navController

Inside when you can put any screens that you want to show navigationBar else just set currentScreen to NoBottomBar

  1. @Composable
  2. private fun NavController.currentScreen(): State<MainSubScreen> {
  3. val currentScreen = remember { mutableStateOf<MainSubScreen>(MainSubScreen.Home) }
  4. DisposableEffect(key1 = this) {
  5. val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
  6. when {
  7. destination.hierarchy.any { it.route == MainSubScreen.Home.route } -> {
  8. currentScreen.value = MainSubScreen.Home
  9. } else -> currentScreen.value = MainSubScreen.NoBottomBar
  10. }
  11. }
  12. addOnDestinationChangedListener(listener)
  13. }
  14. return currentScreen
  15. }

On the Scaffold where you put ur bottomBar

so you can check if currentScreen was NoBottomBar if it was, don’t show it

  1. // initialized currentScreeen above
  2. val currentScreen by navController.currentScreen()
  3. Scaffold(
  4. bottomBar = {
  5. if (currentScreen != MainSubScreen.NoBottomBar) {
  6. MainBottomNavigation()
  7. } else Unit
  8. }
  9. ) {
  10. // Your screen
  11. }

版权声明:本文为lizhenxin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lizhenxin/p/15224190.html