22.3 Compose 沉浸式状态栏 Scaffold 封装

2,523 阅读3分钟

第一节讲了如何实现 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 支持。

93A9C5E3-712B-4C92-81BF-7C09CB60EDDB.png

C9FD8D1A-AD0C-4BD1-8FD5-A7B4466BE545.png

沉浸式 Scaffold 需要设置 contentWindowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)

DF0F88AE-9E3D-4CCF-BAD8-2E131134C2A2.png

添加 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)
        }
    }
861BB97A-A12E-4FB4-9983-F0E6A8DB0EB7.png

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,
    ) {}
79B17F2E-A7D1-47E3-8CE1-B7617DE9718F.png

默认透明时,指定 Icon 颜色

    PageScaffold(
        useDarkModeIcons = {false} // 指定成白色 Icon
    ){}
C487230D-A80B-4B2F-B802-D1A03416DF2A.png

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 的使用,避免发生内容覆盖。

Git 地址