[Android必备基础]之状态栏处理

2,369 阅读12分钟

前言

NavigationBar就是手机底部的虚拟导航按键,在华为和小米手机上很常见;

Android状态栏的处理跟导航栏的处理极其相似,所以文章主要介绍状态栏的处理,需要了解导航栏方面的可以对照状态栏处理看代码进行了解即可,代码在Andme库中已提供;

Android状态栏对整个App而言是非常重要的,直接影响App的展示效果,市面上相关文章和第三方库也非常之多,但质量参差不齐,代码差异化也严重,容易搞得大家云里雾里,甚至一些star比较高的库,其可读性和质量似乎也有待提高;

加之之前曾对这一块的内容做过了解,此次重构Andme时也需要支持状态栏和导航栏的处理,但是却发现自己曾经所做的了解,早已烟消云散,好像自己对此一无所知;俗话说好记性不如烂笔头,特写此文以便未来再次需要了解相关模块时可以作为参考;

状态栏的发展经历过几大阶段,在4.4之前是无法操作状态栏的,可能国内部分厂商有提供相关功能支持。4.4开始官方提供栏状态栏的相关操作,之后5.0进行了完善,6.0做了补充,所以我们主要讲讲4.4、5.0、6.0这三个阶段。

4.4

4.4版本提供的状态栏功能不是很完善,加上目前4.4版本的手机市场占有率不高,基于这两点个人建议可以放弃4.4版本的状态栏支持。

4.4开始,官方提供了透明系统状态栏和沉浸式全屏模式两个概念,官方资料链接,本文只讲状态栏相关的内容(其实状态栏跟沉浸式息息相关);

简单来说,这个版本提供的功能,就是可以将布局内容延伸到状态栏的下面(即状态栏覆盖布局内容),除此之外别无其他,现在市面上的七七八八的各种花里胡哨的功能支持,其实都是经过了二次处理实现的,也正是如此,弄的五花八门,质量参差不齐。

将布局内容填充在状态栏之下有3种方式,其中第一种和第二种方式算是同一种原理的不同形式而已;

1、使用官方提供的新的主题(Deprecated)

使用新的主题Theme.Holo.NoActionBar.TranslucentDecorTheme.Holo.Light.NoActionBar.TranslucentDecor将系统状态栏设置为部分透明;

<style name="AppStyle" parent="@android:style/Theme.Holo.NoActionBar.TranslucentDecor">
	...
</style>

此方式已不建议使用,在5.0以后,建议使用Material主题或者使用AppCompat相关的api; 原文:@deprecated Use Material themes on API 21+ or AppCompat on supported APIs

2、使用主题属性

在主题样式中添加windowTranslucentNavigationwindowTranslucentStatus样式属性

<style name="{Your Theme Name}">
      <item name="windowTranslucentStatus">true</item>
      <item name="windowTranslucentNavigation">true</item>
      <item name="windowContentOverlay">@null</item>
</style>

其本质与第一种类似,第一种也不过就是在提供主题的时候设置了以上属性;

3、代码方式(推荐方式)

使用以下代码即可实现透明状态栏 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

关于WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS的说明:

    /**
       * Window flag: request a translucent status bar with minimal system-provided
       * background protection.
       *
       * <p>This flag can be controlled in your theme through the
       * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute
       * is automatically set for you in the standard translucent decor themes
       * such as
       * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
       * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
       * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
       * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
       *
       * <p>When this flag is enabled for a window, it automatically sets
       * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
       * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
       */
      public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;

注释上已经对4.4透明状态栏的方方面面进行了说明;

上图

俗话说无图无真相,说个JB。

由于电脑存储空间有限,连个4.4的模拟器都运行不了,所以引用了其他博客的图片,如有侵权,请联系删除。

图片来源于:blog.csdn.net/shanshui911… 图片来源https://blog.csdn.net/shanshui911587154/article/details/86623646

可以看到效果就是状态栏覆盖了布局;

小结

也就是说,4.4的状态栏,仅仅只是可以设置将布局内容显示在状态栏之下而已,别无其他,仅此而已,连修改状态栏颜色的功能都没有提供,至此4.4的状态栏功能到此结束,就这么简单,就是这个味儿

什么?怎么就结束了?隔壁王五家提供的库那可是可以设置不同颜色的状态栏哦,对面张三家提供的库更不得了,还可以跟状态栏设置渐变色背景,甚至显示朵花在上面都可以,为什么到你这就啥都没有了;

耶,小老弟别慌别慌,只要你喜欢,把状态栏的背景改成动画都可以,到时装逼绝对酷炫;这到底是怎么回事,听老哥我慢慢道来;

功能扩展

上面的效果图可以明显的看到,状态栏覆盖了布局,但是很多时候需求仅仅只是需要修改状态栏的背景色,其他的照旧。那怎么实现这个功能呢?

其实简单,既然布局可以延伸到状态栏之下,那么我们在布局的顶部放一个跟状态栏一样高度的View不就可以了,既然是我们放置的View,那么想设置什么颜色的背景岂不简单?至于渐变色背景那也不过是小菜一碟,甚至你想弄一个什么玛莎拉蒂奥克斯飞机xxx效果啥的,只要View支持,就都不成问题。

只不过由于大多数界面的需求都是如此,如果每个界面需要设置状态栏颜色我们都在布局中放置一个与状态栏同等高度的View,这不累死人啊,还能愉快的农药么?还能高兴的撸啊撸么?

所以封装,将这一层操作封装起来,这也是大多数第三方库采用的策略。在4.4-5.0之间的版本(其实也就主要是4.4的版本),在DecorView中添加了一个与状态栏等高的View用来控制状态栏的背景色

//以下代码来源于Andme库,其中FakeStatusBar是自定义View,其作用就是将自己的高度设置为状态栏的高度
  override fun setStatusBarColor(activity: Activity, color1: Int, color2: Int, ratio: Float) {
      //透明状态栏:布局内容可以延伸到状态栏之下
      activity.window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
      //查找或添加模拟的状态栏View:查找的目的是避免多次添加
      val statusBar = findOrAddNewFakeStatusBar(activity)
      //计算显示的颜色
      val blendColor = blendColor(activity, color1, color2, ratio)
      //再将颜色作为模拟状态栏view的背景
      statusBar.setBackgroundColor(blendColor)

      //设置contentview fitsSystemWindows,避免contentview被状态栏覆盖
      val rootView = (activity.findViewById(android.R.id.content) as ViewGroup).getChildAt(0) as ViewGroup
      rootView.fitsSystemWindows = true
  }

  /**
   * 查找或添加一个新的模拟的状态栏
   */
  private fun findOrAddNewFakeStatusBar(activity: Activity): FakeStatusBar {
      val decorView = activity.window.decorView as ViewGroup
      var statusBarView: View? = decorView.findViewById(fakeStatusBarId)
      if (statusBarView == null || statusBarView !is FakeStatusBar) {
          statusBarView = FakeStatusBar(activity).apply {
              id = fakeStatusBarId
          }
          decorView.addView(statusBarView)
      }

      if (statusBarView.visibility != View.VISIBLE) {
          statusBarView.visibility = View.VISIBLE
      }
      return statusBarView
  }

万变不离其宗,要封装处理,可以有很多种不同的实现,我们只需要记住上面的小结,记住4.4到底提供了什么功能就好了。

为什么有些库提示要在setContentView之后设置状态栏颜色;这可能是因为DecorView是一个FrameLayout,如果先调用设置状态栏颜色的方法添加了等高的状态栏View,在调用setContentView添加View时,就会覆盖之前添加的等高的状态栏View吧(具体未测试)。

敲黑板

4.4版本只是提供了将布局内容延伸到状态栏之下显示的支持,具体使用方式前面已说明;所以如果要实现类似顶部图片显示在状态栏之下的需求,只需要设置启用这个功能即可实现;对于要控制状态栏背景色的,都是通过在状态栏下面放置一个与状态栏同样高度的View实现的。

5.0

状态栏的处理,不外乎就是想控制状态栏的背景色,或者把布局延伸到状态栏之下显示;在4.4版本提供的状态栏支持中,我们无法直接设置状态栏的背景色,而只能通过一些其他的方式实现,但是在5.0之后,官方提供了设置状态栏背景色的支持;

调用activity.window.statusBarColor = color方法即可设置状态栏的背景色,但是仅仅这样子还不行,我们看一下statusBarColor方法的说明:

    /**
     * Sets the color of the status bar to {@code color}.
     *
     * For this to take effect,
     * the window must be drawing the system bar backgrounds with
     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
     *
     * If {@code color} is not opaque, consider setting
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
     * <p>
     * The transitionName for the view background will be "android:status:background".
     * </p>
     */
    public abstract void setStatusBarColor(@ColorInt int color);

注释上清楚的说明了,要让statsuBarColor生效,必须设置android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,并且不能设置android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS,所以完整代码如下

  val color = xxx
  activity.window.apply {
      //添加关键标志位--状态栏和NavigationBar颜色设置的核心
      addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
      //必须清除这个标志才能使设置的状态栏颜色生效
      clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
      //设置状态栏颜色
      statusBarColor = color
  }

至此,5.0的状态栏的功能到此结束;5.0提供了设置状态栏背景色的支持,用于弥补4.4版本的不足。

诶诶诶,老哥,我这还是有很多疑惑啊,我这样设置之后,布局内容是会延伸到状态栏之下呢,还是不会呀,我这下子有点摸不着了,而且你这无图无真相啊。

我们认真看一下前面的代码,这段代码修改了状态栏的背景色,其清除了WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS标志,诶,这标志有点眼熟啊,好像在哪里见过;这标志是在4.4的时候增加的,其作用前面讲4.4版本时已说明,设置此标志之后,布局内容会延伸到状态栏之下,所以5.0设置状态栏颜色时清除了此标志才能使状态栏背景色生效,那么也就意味着上面的代码只会更改状态栏的背景色,而不会把布局内容延伸到状态栏之下,也就是说状态栏不会覆盖你的布局内容;我们运行一下上面代码看下效果:

那如果想要实现顶部图片显示在状态栏之下怎么处理呢?小老弟,我刚才讲了呀,那个啥啥啥标志,只要我们添加WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,就可实现此功能,跟4.4的方式一模一样。难道就这么简单?当然这么处理之后,由于我们没有设置状态栏颜色,此时的状态栏背景是一个半透明图层,效果如下:

那有没有办法更改这个状态栏背景呢,比如我看别人家的效果都是状态栏透明的;当然有办法,前面不是讲了怎么修改状态栏颜色么,我们将状态栏的背景色修改为透明的不就可以了么。

可是好像冲突了呢,设置状态栏颜色要移除WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS标志,设置状态栏能够覆盖布局要添加这个标志,一个功能实现要添加,一个功能实现不能添加,要同时拥有这两个功能,这不就冲突了么...,这可如何是好,自己也尝试了一下,设置颜色之后添加这个标志,发现状态栏的颜色没有变化,只是布局内容延伸到了状态栏之下(一切尽在意料之中);

我们再看一下WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS的注释说明(前面已提供,翻回去看看),其中有这一段注释:When this flag is enabled for a window, it automatically sets the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}。 这个意思就是说,当跟window添加WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS标志之后,就会自动的跟system UI visiblity flags添加View#SYSTEM_UI_FLAG_LAYOUT_STABLEView#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN这两个标志,至于system UI visibility flags是什么,这里就先不多说(跟前面提到的沉浸式全屏模式息息相关,所以状态栏跟沉浸式全屏模式有着紧密联系),那么这就好办了,我们可以先设置状态栏颜色,然后手动添加这两个标志看行不行,代码如下:

 
        activity.window.apply {
            //添加关键标志位--状态栏和NavigationBar颜色设置的核心
            addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
            //必须清除这个标志才能使设置的状态栏颜色生效
            clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
            //设置状态栏颜色
            statusBarColor = Color.TRANSPARENT
        }
        
         activity.window.decorView.apply {
            val newSysUIFlag = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            if (systemUiVisibility != newSysUIFlag) {
                systemUiVisibility = newSysUIFlag
            }
        }

实际运行效果如下:

至此5.0的状态栏到这里也就结束了。

6.0

4.4开始可以将布局内容延伸到状态栏之下,5.0开始可以设置状态栏背景色,也由此带来一个问题,状态栏的文字和图标是浅色(或白色)的,如果状态栏的背景色也是浅色(或白色),那将导致两者无法看得清楚,这肯定是一个糟糕的体验。要是我们能够设置状态栏文字和图标的颜色就好了。

6.0的出现也正式可以解决上述问题,6.0开始提供了View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR标志位,将状态栏设置为浅色模式;这里有一点比较难理解,添加这个标志位之后,状态栏的文字和图标(只是系统图标,第三方app的图标可能不支持变换)会显示成深色,以兼容状态栏背景色为浅色的模式。

    /**
     * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
     * is compatible with light status bar backgrounds.
     *
     * <p>For this to take effect, the window must request
     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
     *         FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
     *         FLAG_TRANSLUCENT_STATUS}.
     *
     * @see android.R.attr#windowLightStatusBar
     */
    public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;

从注释上可以看到,这个标志位要生效,也必须为window添加android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS标志,并且不能添加android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS ,所以要想使用浅色模式的状态栏,具体代码如下:

	//以下代码来源于Andme库
	//确保标志位生效
      activity.window.apply {
          addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
          clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
      }
      activity.window.decorView.apply {
          systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
      }

运行效果截图,状态栏文字和图标变成了深色:

另外补充一点:国内厂商中小米和魅族,在6.0之前的某些机型中就提供了此功能,但是在后来两个厂商也使用了官方的方式,所以对于这种第三方差异,对于当前的设备占有率考虑,我也认为可以忽略这方面的处理了。

至此状态栏到这里先告一段落,什么水滴屏,刘海屏什么的,就以后再说了,只能感叹一下,写文章不是一点点费时,真的是太费时间了。

Andme Github地址

欢迎入群交流:QQ276097690

更欢迎关注公众号

如果您有更多的建议或者交流,欢迎入群讨论,添加公众号更能第一时间了解最新内容。