我对 Android 的状态栏和导航栏一直有种情结,在我作 Android 开发以前,我就喜欢经过一些 Xposed 插件来让状态栏和导航栏变色或者透明,以消除那丑丑的两个黑条。java
作 Android 开发以后,我更是写了两篇文章(透明状态栏和导航栏的终极解决方案 、Android 状态栏和导航栏的真终极解决方案 )来分析 Android 4.4 以上的状态栏和导航栏的各类效果的实现,还开源了一个库 UltimateBar。git
可是随着本身技术的成长,我愈加以为这个库设计的很差,存在不少缺陷,以致于到了维护不动的境地。好比,常常有人给我提这样的 issues:github
这两个问题,实际上是同一个问题,主要就是由于状态栏和导航栏的设置耦合到了一块儿,致使修改了状态栏,导航栏也会收到影响,固然,我不是故意要把他们耦合到一块儿的,只是要实现不少复杂的功能不得已而为之,不过如今我已经完全解决这个问题了,这个后面会说到。app
基于这些缘由,我最近终于又开发了一个新的库 UltimateBarX,这个名字借鉴了 Google 爸爸的「AndroidX」,Google 以前的 Support 库太混乱了,为了统一,新开了一个「AndroidX」,我起这个名字,多少有点类似的意味。工具
接下拉就言归正传,看看这个库的用法以及它的实现原理。布局
fitsSystemWindows
方法提及View 里面有个方法,叫setFitsSystemWindows
,这个方法有什么用呢,当给 Activity 设置全屏的时候,若是给 contentView
的最外层设置 setFitsSystemWindows(true)
,那么 contentView
就不会侵入状态栏和导航栏,不然,就会侵入。什么意思呢?举个例子,在 Android 5.0 以上,若是在 Activity 中作如下操做字体
int flag = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setStatusBarColor(Color.parseColor("#66000000"));
getWindow().setNavigationBarColor(Color.parseColor("#66000000"));
复制代码
就会看到这样的效果 gradle
若是再在后面加上ui
findViewById(R.id.contentView).setFitsSystemWindows(true);
复制代码
效果就会变成这样 this
以上两个图已经很形象的说明了侵入和非侵入的区别。如今来解释一下为何状态栏和导航栏设置会耦合,从上面的两个图能够看出 setFitsSystemWindows(true)
方法是对状态栏和导航栏同时生效的,就是说要么都侵入,要么都不侵入,那么问题来了,若是我如今要求布局侵入到状态栏而不侵入到导航栏要怎么办呢,其实也是能够实现的,只须要在上面的代码中把 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
去掉,即
int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setStatusBarColor(Color.parseColor("#66000000"));
findViewById(R.id.contentView).setFitsSystemWindows(false);
复制代码
这样就能达到只设置状态栏,而导航栏不受影响的效果,一样的,若是须要让布局只侵入导航栏而状态栏不受影响,只须要
int flag = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
getWindow().getDecorView().setSystemUiVisibility(flag);
getWindow().setNavigationBarColor(Color.parseColor("#66000000"));
findViewById(R.id.contentView).setFitsSystemWindows(false);
复制代码
这样看来好像并无什么问题,各类状况都能解决,可是,若是如今需求是这样的,首先刚进入 Activity 的时候,让布局只侵入到导航栏,而后当点击某个按钮的时候,导航栏保持被侵入不变,同时,让布局侵入到状态栏,这时候若是还用上面的方法,就会致使导航栏被还原成初始状态,如图所示
这是由于在屡次调用 setSystemUiVisibility
方法时,只有最后一次才是有效的,前面的调用都会被最后一次覆盖掉,那要怎么解决这个问题呢,有种方法就是保存当前 Activity 的状态栏和导航栏的状态,当每次须要设置的时候,都根据保存的状态从新设置 setSystemUiVisibility 的参数,保证以前设置过的效果不受影响,可是这样操做起来太过繁琐,我选择了另外一种很简单的方法。
其实方法很简单,当第一次设置状态栏或导航栏的时候,无论须要什么效果,都让布局侵入到状态栏和导航栏,而后根据要不要侵入来设置 decorView
的 topPadding
和 bottomPadding
,好比上面提到的需求,就能够在刚进入 Activity 的时候,就让布局同时侵入到状态栏和导航栏,而后给 decorView
设置一个状态栏高度的 topPadding
,看起来效果就是布局没有侵入到状态栏了,当点击按钮须要让状态栏被侵入的时候,只需再把 decorView
的 topPadding
设为0,而不用再管导航栏,也不用保存导航栏以前设置的状态了,简单粗暴。
UltimateBarX 的用法也很简单,此次我花了很长的时间思考怎样让方法调用起来更简单,同时往后维护起来更方便,首先在 build.gradle 中添加
dependencies {
implementation 'com.zackratos.ultimatebarx:ultimatebarx:0.1.1'
}
复制代码
若是须要设置状态了,能够在 Activity 中
UltimateBarX.create(UltimateBarX.STATUS_BAR) // 设置状态栏
.fitWindow(true) // 布局是否侵入状态栏(true 不侵入,false 侵入)
.bgColor(Color.BLACK) // 状态栏背景颜色(色值)
.bgColorRes(R.color.deepSkyBlue) // 状态栏背景颜色(资源id)
.bgRes(R.drawable.bg_gradient) // 状态栏背景 drawable
.light(false) // light模式(状态栏字体灰色 Android 6.0 以上支持)
.apply(this);
复制代码
方法很是简单,注释也写的很清楚了,这里有三个设置背景的方法,写一个就好了,多写也只有一个会生效,优先级 bgRes
> bgColor
> bgColorRes
,若是须要设置导航栏,在 create
里面传入 UltimateBarX.NAVIGATION_BAR
便可,其余不变,状态栏和导航栏彻底独立设置,互不影响,作到了真正的解耦。
若是要实现上面所说的需求,只须要在刚进入 Activity 的时候
UltimateBarX.create(UltimateBarX.NAVIGATION_BAR)
.fitWindow(false)
.bgColor(Color.parseColor("#66000000"))
.apply(this);
复制代码
设置布局侵入到导航栏,而后点击按钮,让布局侵入到状态栏,只需
UltimateBarX.create(UltimateBarX.STATUS_BAR)
.fitWindow(false)
.bgColor(Color.parseColor("#66000000"))
.apply(this);
复制代码
light
方法这里面有个 light
方法,用来设置状态栏或者导航栏的 light 模式,当 light 模式为 true
时,状态栏的字体会变灰,对应的导航栏的按钮会变灰,这里有个问题,就是设置 light 模式也须要调用 decorView
的 setSystemUiVisibility
方法,这就意味着 light 模式是须要保存状态的,若是不保存,第一次设置状态栏为 light 模式,第二次再设置导航栏的时候,状态栏的 light 模式就会被清除,这个前面已经解释过了。
那如何保存状态呢,为了使侵入性更低,能够用一个单例对象来保存每一个 Activity 的状态栏和导航栏的 light 状态,这样又会有一个问题,在 Activity 退出的时候,必须把当前 Activity 对象从单例里面移除,不然会形成内存泄漏,那么怎样监听 Activity 退出呢,经常使用的作法就是在 Activity 里面添加一个看不见的 Fragment,当 Fragment 的 onDestroy
方法被调用的时候,说明 Activity 的 onDestroy
被调用了,即 Activity 已退出。
不过如今已经不用这么麻烦了,Google 爸爸在 JetPack 组件里面,给咱们提供了很是好用的工具「Lifecycle」,能够无侵入的监听 Activity 和 Fragment 的生命周期,这里就使用 Lifecycle 来实现的,很是方便,其实 Lifecycle 的实现原理也是在内部添加一个 Fragment 来监听的。
原理和用法都讲的差很少了,最后再贴一遍 UltimateBarX 地址,但愿你们多多关注,多多 star、fork,提 issues、提 pr,也欢迎跟我交流