Android 状态栏和导航栏的终极解决方案 最终版

缘起

我对 Android 的状态栏和导航栏一直有种情结,在我作 Android 开发以前,我就喜欢经过一些 Xposed 插件来让状态栏和导航栏变色或者透明,以消除那丑丑的两个黑条。java

作 Android 开发以后,我更是写了两篇文章(透明状态栏和导航栏的终极解决方案 Android 状态栏和导航栏的真终极解决方案 )来分析 Android 4.4 以上的状态栏和导航栏的各类效果的实现,还开源了一个库 UltimateBargit

可是随着本身技术的成长,我愈加以为这个库设计的很差,存在不少缺陷,以致于到了维护不动的境地。好比,常常有人给我提这样的 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 的参数,保证以前设置过的效果不受影响,可是这样操做起来太过繁琐,我选择了另外一种很简单的方法。

UltimateBarX 的核心原理

其实方法很简单,当第一次设置状态栏或导航栏的时候,无论须要什么效果,都让布局侵入到状态栏和导航栏,而后根据要不要侵入来设置 decorViewtopPaddingbottomPadding,好比上面提到的需求,就能够在刚进入 Activity 的时候,就让布局同时侵入到状态栏和导航栏,而后给 decorView 设置一个状态栏高度的 topPadding,看起来效果就是布局没有侵入到状态栏了,当点击按钮须要让状态栏被侵入的时候,只需再把 decorViewtopPadding 设为0,而不用再管导航栏,也不用保存导航栏以前设置的状态了,简单粗暴。

UltimateBarX 的用法

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 模式也须要调用 decorViewsetSystemUiVisibility 方法,这就意味着 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,也欢迎跟我交流

github.com/Zackratos/U…

相关文章
相关标签/搜索