第一节讲了如何实现 Compose 沉浸式状态栏,在项目中还可能会出现不同页面会设置不同状态栏颜色的情况,所以我们这节来将这些功能封装到 Scaffold 中。
WanAndroid 项目主体结构如下:
|--Theme
|--Scaffold
|-- topBar 、bottomBar
|-- content(NavHost)
最外层的 Scaffold 有标题栏、底部导航栏 ,它的 content 是 NavHost 。
每一次路由到其他页面发生重组的是 NavHost 中的 content 。
为了支持每个页面都可以设置状态栏颜色,我们需要封装两个 Scaffold。 一个是用于最外层的 Scaffold , 一个是用于页面的 Scaffold。
FullScreenScaffold
作为最外层的 Scaffold 我们需要做就是设置 SystemBar 透明,同时兼容好 padding 。
@Composable
fun FullScreenScaffold(
modifier: Modifier = Modifier,
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
content: @Composable (PaddingValues) -> Unit
) {
val systemUiController = rememberSystemUiController()
val isUseDarkModeIcons = shouldUseDarkModeIcons()
SideEffect {
transparentSystemBars(systemUiController,isUseDarkModeIcons)
}
Scaffold(
modifier = modifier,
topBar = topBar,
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
contentColor = contentColor,
contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
content = content
)
}
使用 SideEffect 设置 SystemBar 颜色保证每次 Scaffold 重组代码都会被执行。
Scaffold 默认提供了 WindowInsets 支持。
沉浸式 Scaffold 需要设置 contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)
添加 topBar 和 bottomBar 后内容会被遮挡,由于 Scaffold 添加了 WindowInsets 支持,topBar 和 bottomBar 的高度会通过 PaddingValues 的形式传递给 content ,所以要在 content 中使用 PaddingValues 来适配。
WanAndroidTheme(}) {
FullScreenScaffold(
topBar = {
//...
},
bottomBar = {
//...
},
containerColor = Color.LightGray
) {
//添加 padding 适配有 topBar bottomBar
WanNavHost(modifier = Modifier.padding(it),navController = wanAppState.navController)
}
}
PageScaffold
这个 Scaffold 的主要功能是用来设置 SystemBar 背景颜色或 Icon 颜色。
设置 SystemBar 背景颜色的页面 Icon 根据设置的颜色显示
SystemBar 透明的默认跟随系统主题显示,也可以手动设置。
@Composable
fun PageScaffold(
modifier: Modifier = Modifier,
statusBarColor: Color = Color.Transparent,
navigationBarColor: Color = Color.Transparent,
//是否使用暗模式 Icon ,true 灰色, false 白色
useDarkModeIcons:(() -> Boolean)? = null,
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
content: @Composable (PaddingValues) -> Unit
) {
val systemUiController = rememberSystemUiController()
val isUseDarkModeIcons = if (useDarkModeIcons != null){
useDarkModeIcons()
}else{
shouldUseDarkModeIcons(bgColor = statusBarColor)
}
SideEffect {
setSystemBarsColor(systemUiController,isUseDarkModeIcons,statusBarColor, navigationBarColor)
}
Scaffold(
modifier = modifier,
topBar = topBar,
bottomBar = bottomBar,
snackbarHost = snackbarHost,
floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition,
containerColor = containerColor,
contentColor = contentColor,
contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
content = content
)
}
Icon 显示模式由 statusBarColor 决定
PageScaffold(
statusBarColor = Color.Red,
) {}
默认透明时,指定 Icon 颜色
PageScaffold(
useDarkModeIcons = {false} // 指定成白色 Icon
){}
SystemBarsKts
将 SystemBar 操作封装起来,注意 @Composable 方法重组时可能会被忽略,所以两个 Scaffold 中都是使用 SideEffect 来设置的。
@Composable
fun TransparentSystemBars(bgColor: Color = Color.Transparent) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = shouldUseDarkIcons(bgColor = bgColor)
SideEffect {
transparentSystemBars(systemUiController, useDarkIcons)
}
}
@Composable
fun SetStatusBarColor(statusBarColor: Color) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = shouldUseDarkIcons(bgColor = statusBarColor)
SideEffect {
setStatusBarColor(systemUiController, useDarkIcons, statusBarColor)
}
}
@Composable
fun SetNavigationBarColor(navigationBarColor: Color) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = shouldUseDarkIcons(bgColor = navigationBarColor)
SideEffect {
setNavigationBarColor(systemUiController, useDarkIcons, navigationBarColor)
}
}
@Composable
fun SetSystemBarsColor(statusBarColor: Color, navigationBarColor: Color) {
val systemUiController = rememberSystemUiController()
val useDarkIcons = shouldUseDarkIcons(bgColor = statusBarColor)
SideEffect {
setSystemBarsColor(systemUiController, useDarkIcons, statusBarColor, navigationBarColor)
}
}
@Composable
fun SetStatusBarIconDark(isDark:Boolean = true){
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.statusBarDarkContentEnabled = isDark
}
}
fun transparentSystemBars(systemUiController: SystemUiController, useDarkIcons: Boolean) {
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = useDarkIcons,
isNavigationBarContrastEnforced = false,
)
}
fun setStatusBarColor(
systemUiController: SystemUiController,
useDarkIcons: Boolean,
statusBarColor: Color
) {
systemUiController.setStatusBarColor(
color = statusBarColor,
darkIcons = useDarkIcons,
)
}
fun setNavigationBarColor(
systemUiController: SystemUiController,
useDarkIcons: Boolean,
navigationBarColor: Color
) {
systemUiController.setNavigationBarColor(
color = navigationBarColor,
darkIcons = useDarkIcons,
navigationBarContrastEnforced = false
)
}
fun setSystemBarsColor(
systemUiController: SystemUiController,
useDarkIcons: Boolean,
statusBarColor: Color,
navigationBarColor: Color
) {
systemUiController.setStatusBarColor(
color = statusBarColor,
darkIcons = useDarkIcons,
)
systemUiController.setNavigationBarColor(
color = navigationBarColor,
darkIcons = useDarkIcons,
navigationBarContrastEnforced = false
)
}
/**
* 根据 bgColor 亮度判断 systemBar 中 icon 是否使用 暗色模式
* @param bgColor Color systemBar 背景颜色
* @return Boolean true 需要使用 暗色模式 Icon ; false 亮色模式
*/
@Composable
fun shouldUseDarkIcons(bgColor: Color = Color.Transparent):Boolean{
return if (bgColor == Color.Transparent){ // 透明时使用跟随系统主题
!isSystemInDarkTheme()
}else{
//颜色亮度
return bgColor.luminance() >= 0.5
}
}
WanAndroid 项目实现了沉浸式状态栏,使用时注意 Scaffold 提供的 PaddingValues 的使用,避免发生内容覆盖。