Android Jetpack Compose 沉浸式/透明状态栏 ProvideWindowInsets SystemUiController
前言
从现在开始疯狂学习Jetpack Compose,经过一早上的研究带来第一篇文章,沉浸式/透明状态栏
代码
懒得看研究过程的朋友可以直接参考最终结果:
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.accompanist.insets.LocalWindowInsets
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.rememberInsetsPaddingValues
import com.google.accompanist.insets.statusBarsPadding
import com.google.accompanist.systemuicontroller.rememberSystemUiController
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BD_ToolTheme {
ProvideWindowInsets {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
}
Surface(
color = MaterialTheme.colors.background
) {
Column {
com.google.accompanist.insets.ui.TopAppBar(
title = {
Text(text = "首页")
},
contentPadding = rememberInsetsPaddingValues(
insets = LocalWindowInsets.current.statusBars)
)
Text(text = "你好")
}
}
}
}
}
}
}
效果如图:
解析
本篇文章的所有了解来源于这个链接https://google.github.io/accompanist/insets/
当然了,纯英文的,带着翻译工具冲就完事了。
添加依赖
repositories {
mavenCentral()
}
dependencies {
implementation "com.google.accompanist:accompanist-insets:$accompanist_version"
implementation "com.google.accompanist:accompanist-insets-ui:$accompanist_version"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
}
我写下这篇文章时,最新版本为 accompanist_version = "0.15.0"
FitsSystemWindows
第一行代码
WindowCompat.setDecorFitsSystemWindows(window, false)
我们跳转到源码去看看描述
/**
* Sets whether the decor view should fit root-level content views for WindowInsetsCompat.
* If set to false, the framework will not fit the content view to the insets and will just pass through the WindowInsetsCompat to the content view.
* Please note: using the View.setSystemUiVisibility(int) API in your app can conflict with this method. Please discontinue use of View.setSystemUiVisibility(int).
* Params:
* window – The current window.
* decorFitsSystemWindows – Whether the decor view should fit root-level content views for insets.
*/
public static void setDecorFitsSystemWindows(@NonNull Window window,
final boolean decorFitsSystemWindows) {
if (Build.VERSION.SDK_INT >= 30) {
Impl30.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
} else if (Build.VERSION.SDK_INT >= 16) {
Impl16.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
}
}
可以看出,当设置成false,就不再使用原定的间隔框架,我们的页面也就不再受状态栏的影响。
If set to false, the framework will not fit the content view to the insets and will just pass through the WindowInsetsCompat to the content view.
如果设置为false,框架将不会将内容视图与嵌入物相适应,而只是将WindowInsetsCompat传递给内容视图。
文字描述总是乏味的,上代码
- 首先,将decorFitsSystemWindows设置为true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, true)
setContent {
BD_ToolTheme {
Text(text = "首页\r\n首页1\r\n首页2\r\n首页3")
}
}
}
效果如下:
可以看到第一个首页,是紧贴着状态栏的。
- 接着,将decorFitsSystemWindows设置为false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BD_ToolTheme {
Text(text = "首页\r\n首页1\r\n首页2\r\n首页3")
}
}
}
效果如下:
可以看到第一个首页,其实已经藏在了状态栏的后面了。
透明状态栏
上一步我们已将内容铺满全屏了,这一步就应该将状态改成透明,将我们的内容显示出来。
而这一步我们用到的就是SystemUiController。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BD_ToolTheme {
rememberSystemUiController().setStatusBarColor(
Color.Transparent, darkIcons = MaterialTheme.colors.isLight)
Text(text = "首页\r\n首页1\r\n首页2\r\n首页3")
}
}
}
拿到 SystemUiController 的实例,是使用rememberSystemUiController()这个方法,看到remember开头,就知道这是一个观察者的值,然后使用 SystemUiController 的实例去调用方法setStatusBarColor,这个方法有3个参数
fun setStatusBarColor(
// 状态栏的颜色
color: Color,
// 是否为深色图标
darkIcons: Boolean = color.luminance() > 0.5f,
// 如果要求使用深色图标但没有,将调用一个lambda来转换颜色。默认情况下是应用一个黑色框框。
transformColorForLightContent: (Color) -> Color = BlackScrimmed
)
我们设置颜色为透明Color.Transparent
,是否为深色图标就根据是否在深色模式来判断MaterialTheme.colors.isLight
内容不挡住状态栏
上一步已经将内容显示出来的,那么怎么将它往下移一点,不要和状态栏给重叠了呢?
这就要用到ProvideWindowInsets了。我们需要在显示内容的外围包一层ProvideWindowInsets,这样子,我们的显示内容才能读取到真正的状态栏高度之类的值。
举例:我们先不用ProvideWindowInsets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BD_ToolTheme {
rememberSystemUiController().setStatusBarColor(
Color.Transparent, darkIcons = MaterialTheme.colors.isLight)
Column {
// 先显示一个空白在上面,高度是状态栏高度
Spacer(modifier = Modifier
.statusBarsHeight()
.fillMaxWidth())
Text(text = "首页\r\n首页1\r\n首页2\r\n首页3")
}
}
}
}
可以看出Spacer并没有生效,说明这时候读取到的状态栏高度是0.
这个时候我们加入ProvideWindowInsets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BD_ToolTheme {
// 加入ProvideWindowInsets
ProvideWindowInsets {
rememberSystemUiController().setStatusBarColor(
Color.Transparent, darkIcons = MaterialTheme.colors.isLight)
Column {
Spacer(modifier = Modifier
.statusBarsHeight()
.fillMaxWidth())
Text(text = "首页\r\n首页1\r\n首页2\r\n首页3")
}
}
}
}
}
明显可以看出Spacer生效了,能读取到系统状态栏的高度了
可以在跳转进statusBarsHeight()这个方法,可以看到其实是读取了这个值:
LocalWindowInsets.current.statusBars
,所以也可以改写成rememberInsetsPaddingValues(insets = LocalWindowInsets.current.statusBars)
完事
到这里基本的逻辑已经讲完了,可能有同学发现有个东西我没有讲到SideEffect
,为什么需要用这个来包着我们的状态栏颜色设置呢?这个有机会再开一篇文章来说。