Jetpack Compose之隐藏Scaffold的BottomNavigation

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

@Composable
fun Greeting(vm: VM) {
    val list = listOf("One", "Two", "Three")
    var selectedItem = remember {
        mutableStateOf(0)
    }
    val navController = rememberNavController()

    Scaffold(bottomBar = {
        state.takeIf { it.value }?.let {
            BottomNavigation {
                list.forEachIndexed { index, label ->
                    BottomNavigationItem(
                        label = { Text(text = label) },
                        selected = index == selectedItem.value,
                        onClick = { selectedItem.value = index },
                        icon = {
                            Icon(
                                painter = painterResource(id = R.drawable.ic_launcher_foreground),
                                contentDescription = null
                            )
                        })
                }
            }
        }
    }) {
        NavHost(navController = navController, startDestination = "one") {
            composable(route = "one") { PageList(navController, vm) }
            composable(route = "detail") { PageDetail(vm) }
        }
    }
}

Jetpack Compose之隐藏Scaffold的BottomNavigation

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

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

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

// ViewModel
class VM: ViewModel() {
  private val _state: MutableLiveData<Boolean> = MutableLiveData(true)
  val state: LiveData<Boolean> get() = _state
  fun setState(status: Boolean) {
      _state.postValue(status)
  }
}


// MainPage
@Compose MainPage(vm: VM) {
  LaunchedEffect(key1 = true) {
    vm.setState(true)
  }

  DisposableEffect(key1 = true) {
    onDispose {
      vm.setState(false)
    }
  }
}


// page contains Scaffold
@Composable
fun Greeting(vm: VM) {
  // State of BottomNavigation`s visibility
  val state = remember { mutableStateOf<Boolean>(true) }
  // read the BottomNavigation`s visibility from ViewModel and send to State
  vm.state.observeAsState().value?.let { state.value = it }

  Scaffold(bottomBar = {
    // show / hide BottomNavigation controlled by State
    state.takeIf { it.value }?.let {
        BottomNavigation {
            list.forEachIndexed { index, label ->
                BottomNavigationItem(
                    label = { Text(text = label) },
                    selected = index == selectedItem.value,
                    onClick = { selectedItem.value = index },
                    icon = {
                        Icon(
                            painter = painterResource(id = R.drawable.ic_launcher_foreground),
                            contentDescription = null
                        )
                    })
            }
        }
    }
  }) {
    NavHost(navController = navController, startDestination = "one") {
        composable(route = "one") { PageList(navController, vm) }
        composable(route = "detail") { PageDetail(vm) }
      }
    }
  }

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


我在*上提问时有人回答了另一个办法。这个办法是给每个屏幕添加标志位,来区分是否是导航的主页,之后再创建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

@Composable
private fun NavController.currentScreen(): State<MainSubScreen> {
    val currentScreen = remember { mutableStateOf<MainSubScreen>(MainSubScreen.Home) }

    DisposableEffect(key1 = this) {
        val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
            when {
                destination.hierarchy.any { it.route == MainSubScreen.Home.route } -> {
                    currentScreen.value = MainSubScreen.Home
                } else -> currentScreen.value = MainSubScreen.NoBottomBar
            }
        }
        addOnDestinationChangedListener(listener)
    }
    return currentScreen
}

On the Scaffold where you put ur bottomBar

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

// initialized currentScreeen above
val currentScreen by navController.currentScreen()
    Scaffold(
        bottomBar = {
            if (currentScreen != MainSubScreen.NoBottomBar) {
                MainBottomNavigation()
            } else Unit
        }
    ) {
        // Your screen
    }
上一篇:jetpack实战之startup处理应用程序启动初始化


下一篇:Android Jetpack架构之LiveData