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

缘起

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

从 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"));


就会看到这样的效果

如果再在后面加上

findViewById(R.id.contentView).setFitsSystemWindows(true);

效果就会变成这样

以上两个图已经很形象的说明了侵入和非侵入的区别。现在来解释一下为什么状态栏和导航栏设置会耦合,从上面的两个图可以看出 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,也欢迎跟我交流

https://github.com/Zackratos/UltimateBarX


作者:Zackratos
链接:https://juejin.im/post/5f03e8786fb9a07e5e58c623

关注我获取更多知识或者投稿

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值