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

去年我写过一篇文章,透明状态栏和导航栏的终极解决方案,并在 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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容