前言
网上有很多文章介绍如何通过代码实现沉浸式页面,但笔者更喜欢通过 style 进行配置,本文将介绍如何通过 style 实现沉浸式界面,以及如何让布局适配沉浸式界面。
沉浸
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="ImmersiveTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
<item name="android:enforceNavigationBarContrast" tools:targetApi="q">false</item>
</style>
</resources>
对你的 Activity 或 Dialog 使用这个 style,就会有以下效果:
- 状态栏透明。
- 导航栏半透明。
- 布局绘制到了状态栏和导航栏区域
Q:为什么导航栏不是全透明?
A:笔者研究了很久也没解决,即使参考用代码实现的方式转为 style 参数进行配置也无果,笔者不才,如果有大佬知道怎么解决请指点一下我,非常感谢!(从另一个角度看,半透明的导航栏既能帮用户区分点击区域,也能确保导航按钮能有足够的对比度)
适配
简单来说就是处理 状态栏&导航栏高度的适配问题,常见的做法有两种:
- 做法1:手动计算状态栏&导航栏的高度,然后修改padding、margin、View高度等。
主要问题有2个:
- 有些厂商的 ROM 用常规方式获取状态栏&导航栏的高度可能不对。
- 需要主动监听用户更改导航方式并刷新布局。(比如用户从“三按钮”导航改成“手势”导航,导航栏的高度发生变化)
- 做法2:使用
fitsSystemWindows。
主要问题有1个:
- 当使用这个属性的时候一定会在顶部和底部同时产生padding,当开发者希望只适配状态栏或只适配导航栏的情况下(比如笔者就遇到策划希望让界面底下的壁纸延申到状态栏但是不要延申到导航栏),这个属性无法处理。
下面给出笔者使用的解决方案:WindowInsets
class FitInsetsFrameLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
var fitLeft: Boolean = false
var fitTop: Boolean = true
var fitRight: Boolean = false
var fitBottom: Boolean = true
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
WindowInsetsCompat.toWindowInsetsCompat(insets).let { windowInsetsCompat ->
val systemBarsInsets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars())
val targetPaddingLeft = if (fitLeft) systemBarsInsets.left else 0
val targetPaddingTop = if (fitTop) systemBarsInsets.top else 0
val targetPaddingRight = if (fitRight) systemBarsInsets.right else 0
val targetPaddingBottom = if (fitBottom) systemBarsInsets.bottom else 0
if (paddingLeft != targetPaddingLeft ||
paddingTop != targetPaddingTop ||
paddingRight != targetPaddingRight ||
paddingBottom != targetPaddingBottom) {
setPadding(targetPaddingLeft, targetPaddingTop, targetPaddingRight, targetPaddingBottom)
}
}
return insets
}
}
代码是简化过的,但相信各位看官们能看懂代码主要做了什么,借助系统下发的 WindowInsets 数据,可以监听到用户更改操作方式的事件,并准确获取状态栏&导航栏的高度,剩下的只需要按需求选择性的适配某个(些)方向上的沉浸即可。
如果觉得要写自定义布局比较麻烦,可以看下笔者开发的 gadgets,里面封装了一个工具 InsetFit,只需一句代码就可以使你的 Layout 具备适配状态栏&导航栏的功能:
class MainActivity {
override fun onCreate(...) {
InsetFit(YourLayout, top = true, bottom = true)
}
}
布局上的适配完成,最后顺便说一下“状态栏&导航栏上元素的适配”(比如app延申到状态栏的背景色是白色,如何让状态栏上的文字和图标显示深色),以及如何“隐藏/显示 状态栏&导航栏”。
class MainActivity {
private lateinit var windowInsetsControllerCompat: WindowInsetsControllerCompat
override fun onCreate(...) {
windowInsetsControllerCompat = WindowInsetsControllerCompat(window, window.decorView)
// 状态栏/导航栏 显示浅色或深色
windowInsetsControllerCompat.isAppearanceLightStatusBars = true or false
windowInsetsControllerCompat.isAppearanceLightNavigationBars = true or false
// 提前设置 状态栏/导航栏 的行为
windowInsetsControllerCompat.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
override fun onConfigurationChanged(...) {
if (要隐藏 状态栏&导航栏) { // 比如全屏(横屏)
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars())
} else if(要显示 状态栏&导航栏) {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.systemBars())
}
}
}
总结
没啥好总结的,感觉文章的内容都挺简单的,但笔者当初确实踩了不少坑,最后总结出这样一份沉浸式方案,希望各位看官或多或少有点收获。
笔者不才,行文多有不妥之处,请各位看官指点与见谅!能点个赞或给笔者github来个star就更好了哈哈~