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

96
Zackratos
4.5 2018.11.08 18:19* 字数 2210

去年我写过一篇文章,透明状态栏和导航栏的终极解决方案,并在 Github 上开源了代码,https://github.com/Zackratos/UltimateBar,其实在那之后,我一直对这个项目进行维护和更新,最近,我又用 kotlin 重构了代码,并加入了一些新的功能,代码改动比较大,特意写篇文章介绍一下,希望这个库能给做 Android 开发的同学带来帮助。

旧版本的缺陷

事实上,这次重构之后已经是 3.0 版本了,之前重构 2.0 版本的时候已经有了大幅度的修改,但 2.0 版的重构主要是对代码的优化,方便开发者的调用,比如采用 Builder 模式进行配置参数。而这次的重构,在功能上做了很大的修改,可以适用于更多的场景。

旧版本主要有 3 个缺陷。

  1. 参数过多。旧版本中设置状态栏和导航栏的颜色的时候,参数包括了色值和透明度以及颜色的深度,导致调用的时候需要传入大量的参数,其实这些完全没必要,透明度和颜色深度都是可以包含在色值里面的,严格来说,只要传入一个参数就好了,至于它的透明度和深度,如果需要,开发者可以自己设置好之后再传入。

  2. 同一个 Activity 只能设置一次。旧版本中一般需要使用透明状态栏和导航栏的时候,都是在 onCreate 中设置,然后就固定了,但是如果有时候需要在 Activity 中再次设置不同的效果,就会力不从心了。

  3. 不支持灰色模式。我们知道, Android 6.0 以上是支持状态栏灰色模式的,就是把状态栏种的字体颜色改为灰色,这种模式是为了避免在白色界面上设置沉浸状态栏导致状态栏的字体看不见的尴尬的,但是旧版本中并没有对这种情况进行适配。

新版本的改进

针对旧版本的缺陷,新版本主要做了以下改进。

  1. 去除多余的参数。新版本中状态栏和导航栏的背景都只需要一个参数,参数类型是 Drawable,相对于 Color 来说,Drawable 明显更加灵活,不但可以设置状态栏和导航栏的颜色,而且可以设置透明度,还有渐变色等各种效果,也就是说,只要是 Drawable 能实现的效果,都能设置到状态栏和导航栏上。

  2. 支持多次设置。新版本中调整了代码的逻辑,现在可以对状态栏和导航栏的效果进行多次设置了,但是 UltimateBar 有四种效果,多次设置只能对同一种效果有效。比如说,如果你第一次设置了半透明的状态栏和导航栏,那么后面要修改,也只能设置半透明的效果,如果设置其他效果会出现不可预知的问题。

  3. 支持灰色模式,分别对 Android 6.0 以上和 Android 8.0 以上适配了状态栏灰色模式和导航栏灰色模式,避免白色界面状态栏字体看不见的尴尬。

使用

UltimateBar 的使用非常简单,首先在 gradle 中添加依赖

implementation 'com.github.zackratos.ultimatebar:ultimatebar3:3.0.0'
  1. 如果你要直接给状态栏和导航栏设置一个背景,在 onCreate 中
UltimateBar.Companion.with(this)
        .statusDark(false)                  // 状态栏灰色模式(Android 6.0+),默认 flase
        .statusDrawable(drawable)           // 状态栏背景,默认 null
        .applyNavigation(true)              // 应用到导航栏,默认 flase
        .navigationDark(false)              // 导航栏灰色模式(Android 8.0+),默认 false
        .navigationDrawable(drawable)       // 导航栏背景,默认 null
        .create()
        .drawableBar();
  1. 如果要给状态栏和导航栏设置半透明效果,在 onCreate 中
UltimateBar.Companion.with(this)
        .statusDark(false)                  // 状态栏灰色模式(Android 6.0+),默认 flase
        .statusDrawable(drawable)           // 状态栏背景,默认 null
        .applyNavigation(true)              // 应用到导航栏,默认 flase
        .navigationDark(false)              // 导航栏灰色模式(Android 8.0+),默认 false
        .navigationDrawable(drawable)       // 导航栏背景,默认 null
        .create()
        .transparentBar();

注意这里的 statusDrawable 和 navigationDrawable 的参数必须是半透明的,因为此时 contentView 会延伸到状态栏和导航栏上面,如果不是半透明的,会把 contentView 盖住。

  1. 如果要设置沉浸式状态栏和导航栏,在 onCreate 中
UltimateBar.Companion.with(this)
        .statusDark(false)                  // 状态栏灰色模式(Android 6.0+),默认 flase
        .applyNavigation(true)              // 应用到导航栏,默认 flase
        .navigationDark(false)              // 导航栏灰色模式(Android 8.0+),默认 false
        .create()
        .immersionBar();

其实这就是第二种效果的特殊情况,即 statusDrawable 和 navigationDrawable 都为 null。

  1. 如果要隐藏状态栏和导航栏,需要重写 onWindowFocusChanged 方法
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        UltimateBar.Companion.with(this)
                .applyNavigation(true)      // 是否应用到导航栏
                .create()
                .hideBar();
    }
}
  1. 如果使用了 DrawerLayout,需要实现 DrawerLayout 的主布局被覆盖,而抽屉需要沉浸的效果,可以在 onCreate 中
UltimateBar.Companion.with(this)
        .statusDark(false)                  // 状态栏灰色模式(Android 6.0+),默认 flase
        .statusDrawable(drawable)           // 状态栏背景,默认 null
        .applyNavigation(true)              // 应用到导航栏,默认 flase
        .navigationDark(false)              // 导航栏灰色模式(Android 8.0+),默认 false
        .navigationDrawable(drawable)       // 导航栏背景,默认 null
        .create()
        .drawableBarDrawer(drawerLayout,    // DrawerLayout
                content,                    // DrawerLayout 的主布局 View
                drawer);                    // DrawerLayout 的抽屉布局 View
  1. 以上所有的方法,如果是在 kotlin 中使用,都可以省略 Companion 关键词,并且 kotlin 中可以用如下方式调用
with().statusDark(false)                  // 状态栏灰色模式(Android 6.0+),默认 flase
        .statusDrawable(drawable)           // 状态栏背景,默认 null
        .applyNavigation(true)              // 应用到导航栏,默认 flase
        .navigationDark(false)              // 导航栏灰色模式(Android 8.0+),默认 false
        .navigationDrawable(drawable)       // 导航栏背景,默认 null
        .create()
        .drawableBar();

with() 就相当于 UltimateBar.Companion.with(this)。

  1. 如果要在 Fragment 中使用,可以先设置 Fragment 所在的 Activity 沉浸,然后再设置 Fragment, 可以参考 demo 中的写法。

终极方案

在实际开发中,有时会遇到一些奇葩的需求,各种复杂的情况叠加,这时候可以采用终极解决方案,就是先把状态栏和导航栏都设置为沉浸式,然后通过在状态栏和导航栏的位置加一个自定义背景的 View 和应对复杂的业务场景,具体使用可参考 demo。

简单原理

状态栏和导航栏的主要难点是 Android 4.4 和 Android 5.0 以上的实现方法不一样,要达到一致的效果,必须进行版本适配。

在 Android 4.4 上只能设置状态栏和导航栏透明,如果要设置它们的背景,必须在状态栏和导航栏的位置手动添加一个 View,并设置 View 的背景来充当状态栏和导航栏的背景,而在 Android 5.0 以上,可以直接设置状态栏和导航栏的背景色。

但是在 Android 5.0 以上的 Api 中,状态栏和导航栏的背景只能设置为 Color,不能是 Drawable,为了设置 Drawable,在 Android 5.0 以上,同样先设置状态栏和导航栏透明,然后也在状态栏和导航栏的位置上添加一个 View,用 View 的背景来充当状态栏和导航栏的背景,从而可以直接设置为 Drawable。

为什么不把状态栏和导航栏分开设置

其实在重构 2.0 版本的时候,我就考虑把状态栏和导航栏分开,分别设置,互不影响,这样代码更优雅,调用起来也更方便,但是后来发现一个问题,就是导航栏不能单独设置,如果单独设置了导航栏沉浸,状态栏也会出现沉浸的效果。但是可以单独设置状态栏沉浸,导航栏保持原状,基于此原因,UltimateBar 设计为统一的 Api,然后通过参数 applyNavigation 来确定要不要应用到导航栏中。

不过我现在又想到一个分开设置的野路子方法,就是每种情况都先设置成沉浸的效果,然后通过修改 mContentParent 的 paddingTop 和 paddingBottom 来设置状态栏和导航栏的效果,这样就可以把状态栏和导航栏分开来设置了,可以考虑在下个版本中使用这种方法。

存在的不足

目前 UltimateBar 还存在以下几点不足。

  1. 前面提到的,多次设置只能针对同一种效果,不同效果会有不可预估的问题,不过绝大多数情况下不会有这样的需求。

  2. 也是前面提到的,状态栏和导航栏没有分开设置,现在只能单独设置状态栏或者同时设置状态栏和导航栏,不能单独设置导航栏,不过绝大多数情况不会有单独设置导航栏这种奇葩的需求。

  3. 现在的状态栏灰色模式只支持 Android 6.0 以上,因为官方是 Android 6.0 才开始支持的,但其实 miui 和 flyme 从 Android 4.4 就开始支持了,但是由于我身边没有相应的设备,不好测试,所以暂时没有针对 miui 和 flyme 做专门的适配。

以上三点不足,其实都影响不大,之所以称它们为“不足”,而不是“问题”,就是因为影响很小,不夸张的说,UltimateBar 绝对可以满足绝大多数需求。

最后,奉上 Github 地址

https://github.com/Zackratos/UltimateBar

欢迎 star、fork,提 issue、pull request。

Android
Web note ad 2