Android 状态栏与主题探究

前不久在写一个功能的时候,遇到一个添加引导页的需求,直接照搬了之前的引导页的实现方式,使用了 NewbieGuide 来实现,发现在 Activity 中引导页弹出时无法遮盖状态栏,除了状态栏外的部分才能正常被遮盖,所以探究下这个是什么原因导致的

图片名称 图片名称 图片名称

项目中其他地方的遮罩可以完整的盖住整个手机屏幕,连同状态栏一起,所以先去看看这些地方是如何实现的,一共有两种:

  1. 布局中设置 android:fitsSystemWindows=”true” 的同时,使用了自定义的 TransLinearLayout
    • 这个 TranslinearLayout 中的 onMeasure 方法中,加上了 WindowInsets 的 getSystemWindowInsetTop 取得的高度

      而 WindowInsets 这里的 inset 的直译是插入物,可以理解为特定屏幕区域,WindowInsets 的三个成员变量 mSystemWindowInsets,mWindowDecorInsets,mStableInsets 表示了三种屏幕区域,而 SystemWindowInsets 系统窗口区域代表着整个屏幕窗口上,状态栏,导航栏,输入法等系统窗口占用的区域


      这里的top表示的是顶部占位区域的宽度,即状态栏的宽度,长度默认是屏宽
  2. 某些 Activity 的 Theme 中加上 windowTranslucentStatus=true 属性,或者在活动的 onCreate 代码中设置
    1
    <item name="android:windowTranslucentStatus">true</item>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
    }

    setContentView(R.layout.ai_kline_act_kline_master)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    val titleBar = findViewById<ConstraintLayout>(R.id.ai_kline_master_title_bar)
    var statusBarHeight = 60
    if (ScreenUtil.getStatusBarHeight(this) != 0) {
    statusBarHeight = ScreenUtil.getStatusBarHeight(this)
    }

    titleBar.setPadding(titleBar.paddingLeft, titleBar.paddingTop + statusBarHeight, titleBar.paddingRight, titleBar.paddingBottom)

    val titleBarLayoutParams = titleBar.layoutParams
    titleBarLayoutParams.height = titleBarLayoutParams.height + statusBarHeight
    titleBar.layoutParams = titleBarLayoutParams
    }

    至此明白了需要怎样去实现,解决了问题,但是从这里可以看出我对主题,状态栏相关的知识不够扎实,故对相关内容重新整理归纳一下

首先是主题相关的内容,Android 5.0 发布后,support-v7-appcompat 也更新到V21,增加了 ToolBar、recyclerview、cardview 等控件。Android 从 5.0 开始支持 Material Design 应用,可以通过修改各项参数来达到不同的主题效果,以下是各项参数的作用

  • textColorPrimary 应用的主要文字颜色,ActionBar 的标题文字默认使用该颜色
  • colorPrimary 应用的主要色调,actionBar 默认使用该颜色,Toolbar 导航栏的底色
  • statusBarColor 状态栏颜色,默认使用 colorPrimaryDark
  • colorPrimaryDark 应用的主要暗色调,statusBarColor 默认使用该颜色
  • windowBackground 窗口背景颜色
  • navigationBarColor 底部栏颜色
  • colorAccent CheckBox,RadioButton,SwitchCompat 等一般控件的选中效果默认采用该颜色
  • colorForeground 应用的前景色,ListView的分割线,switch 滑动区默认使用该颜色
  • colorBackground 应用的背景色,popMenu 的背景默认使用该颜色
  • colorControlNormal CheckBox,RadioButton,SwitchCompat 等默认状态的颜色。
  • colorControlHighlight 控件按压时的色调
  • colorControlActivated 控件选中时的颜色,默认使用 colorAccent
  • colorButtonNormal 默认按钮的背景颜色
  • editTextColor :默认 EditView 输入框字体的颜色。
  • textColor Button,textView 的文字颜色
  • textColorPrimaryDisableOnly RadioButton checkbox 等控件的文字
  • colorSwitchThumbNormal switch thumbs 默认状态的颜色. (switch off)
1
2
3
4
<item name="windowNoTitle">false</item>
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">false</item>
<item name="android:windowActionBar">false</item>

又发现到 AppCompatActivity 中出现了 windowNoTitle 属性,前边不需要加 “android:”,那这两个有什么区别吗?

其实带 android 的是系统的,不带 android 是兼容样式的,就是 Theme.AppCompat 样式的,因为是 support 包里重新定义了这个属性,即 windowNoTitle 是 appcompat-v7 中的属性,在 appcompat-v7\res\values\values.xml 中定义了,可以打开 appcompat-v7\res\values\values.xml 搜索 AppCompatTheme,可以找到定义的 “windowNoTitle”、”windowActionBar” 等属性。

“windowNoTitle” 属性在代码中可以使用 R.attr.windowNoTitle 访问,
“android:windowNoTitle” 则需要使用 android.R.attr.windowNoTitle 访问。

使用 AppCompatActivity 时(Activity 必须使用 Theme.AppCompat 主题及其子主题),测试发现:

  1. “android:windowActionBar” 属性在 AppCompatActivity 中不起作用

  2. windowNoTitle = false 并且 android:windowNoTitle = false 时,会出现两个标题,位于下方的是 AppCompatActivity的标题栏

    图片名称
  3. 当windowNoTitle = false,windowActionBar = false 时,会报错: AppCompat does not support the current theme features

  4. windowNoTitle = true 并且 android:windowNoTitle = true时 ,无标题

然后是状态栏的沉浸式实现的转变:

  1. Android4.4(API 19)- Android 5.0(API 21): 这个阶段可以实现沉浸式,但是表现得还不是很好,实现方式为: 通过 FLAG_TRANSLUCENT_STATUS 设置状态栏为透明并且为全屏模式,然后通过添加一个与 StatusBar 一样大小的 View,将 View 的 background 设置为我们想要的颜色,或者是像上文那样动态设置一个 padding,从而来实现沉浸式

    这里的 FLAG_TRANSLUCENT_STATUS 的含义是:设置状态栏透明,并且变为全屏模式,从注释中看到,当 window 的这个属性有效的时候,会自动设置 system ui visibility 的标志 SYSTEM_UI_FLAG_LAYOUT_STABLE 和 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

  2. Android 5.0(API 21)以上版本: 在Android 5.0 的时候,加入了一个重要的属性和方法 android:statusBarColor(对应方法为 setStatusBarColor),通过这个方法我们就可以轻松实现沉浸式

  3. Android 6.0(API 23)以上版本:Android 6.0 以上的实现方式和 Android 5.0 +是一样,但是从 Android 6.0(API 23)开始,我们可以改状态栏的绘制模式,可以显示白色或浅黑色的内容和图标(除了魅族手机,魅族自家有做源码更改,6.0 以下就能实现)示例

    1
    2
    //6.0 以上可以设置状态栏的字体为黑色,使用下面代码可以打开亮色状态栏模式,实现黑色字体,白底的需求用这句 setStatusBarColor(Color.WHITE)
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

    更新

    同样的操作,在 Android 8.0 以上似乎并不能很好地实现,而是更符合 flag 原本的定义,FLAG_TRANSLUCENT_STATUS 其中的 TRANSLUCENT 的意思是半透明的意思,所以在 8.0 以上的时候,设置这个 FLAG 会让状态栏半透明,同时 SYSTEM_UI_FLAG_LAYOUT_STABLE(让应用的主体内容占用系统状态栏的空间)和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(全屏)也会生效,造成顶部和状态栏重叠的情况,同时有一条半透明的状态栏在上面,这样子的话,在 Android7.0 中可以完美实现的沉浸式,在 8.0 的时候还是会显示一条半透明状态栏,所以如果要实现沉浸式,设置 FLAG_TRANSLUCENT_STATUS 和 android:windowTranslucentStatus = true 其实并不是太可取,应该要动态改变状态栏的颜色来实现真正的沉浸式,可以设置状态栏颜色与下方控件颜色一致,或者在没有设置 FLAG_TRANSLUCENT_STATUS 而设置了 FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS 的同时,设置状态栏颜色为透明,因为设置 FLAG_TRANSLUCENT_STATUS 会使得状态栏默认为半透明颜色


Android 状态栏与主题探究
https://enderhoshi.github.io/2019/02/13/Android 状态栏与主题探究/
作者
HoshIlIlI
发布于
2019年2月13日
许可协议