安卓沉浸式状态栏

前言

最近在研究安卓沉浸式状态栏,看了很多国内的博客,说的都很乱,在此就自己的心得进行总结。本文将根据安卓版本的不同,实现方式不同(xml实现,代码实现)以及实现功能不同(图片沉浸,状态栏与标题栏同色)分别进行说明。

安卓版本

Android4.4(API 19) - Android 5.0(API 21): 这个阶段可以实现沉浸式,但是表现得还不是很好,实现方式为: 通过FLAG_TRANSLUCENT_STATUS设置状态栏为透明并且为全屏模式,然后通过添加一个与StatusBar 一样大小的View,将View 的 background 设置为我们想要的颜色,从而来实现沉浸式。
Android 5.0(API 21)以上版本: 在Android 5.0的时候,加入了一个重要的属性和方法 android:statusBarColor (对应方法为 setStatusBarColor),通过这个方法我们就可以轻松实现沉浸式。也就是说,从Android5.0开始,系统才真正的支持沉浸式。
Android 6.0(API 23)以上版本:其实Android6.0以上的实现方式和Android 5.0 +是一样,为什么要将它归为一个单独重要的阶段呢?是因为从Android 6.0(API 23)开始,我们可以改状态栏的绘制模式,可以显示白色或浅黑色的内容和图标(除了魅族手机,魅族自家有做源码更改,6.0以下就能实现)

Android4.4之前

在Android4.4之前,我们的应用没法改变手机的状态栏颜色,当我们打开应用时,会出现上图中左侧的画面,在屏幕的顶部有一条黑色的状态栏,和应用的风格非常不协调。

Android4.4(API 19) - Android 5.0(API 21)

安卓4.4之后支持沉浸式状态栏,但是和安卓5.0之后有点区别。

1.图片沉浸

效果如图:
在这里插入图片描述

1.1 xml实现

<style name="NewStyle" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowTranslucentStatus">true</item> 系统状态栏透明 <item name="android:windowTranslucentNavigation">true</item> 系统导航栏透明,不在本文范围内,可以不加。 </style>

这会导致布局充满到状态栏和导航栏中!如图左:

在这里插入图片描述 在这里插入图片描述
如果不希望图片沉浸入状态栏,可以在根布局加入 android:fitsSystemWindows="true"
此属性时使布局时考虑系统布局的位置(状态栏、导航栏等),所以不会遮挡状态栏和导航栏。状态栏和导航栏会和根布局相同颜色,此时也失去了将图片沉浸的意义,这种方式适合含有toolbar时。

1.2代码实现

/** * 设置状态栏颜色 * * @param activity 需要设置的activity * @param color 状态栏颜色值 */
    public static void setTranslucentStatus(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
        &&Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {//判断安卓版本是否大于4.4
            // 设置状态栏透明
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            // 设置根布局android:fitsSystemWindows="true,不需要可不加
            ViewGroup rootView = (ViewGroup) ((ViewGroup)activity.findViewById(android.R.id.content)).getChildAt(0);
            rootView.setFitsSystemWindows(true);

2.ToolBar沉浸

由上面可知,图片沉浸到状态栏中。但有时候,比如含有ToolBar时,我们并不想把Toolbar布局沉浸到状态栏中,因为这会使状态栏压盖ToolBar,如图左。我们希望ToolBar和状态栏颜色相同,如图右。
在这里插入图片描述在这里插入图片描述

2.1xml实现

2.1.1 设置 fitsSystemWindows 属性

在根布局上设置android:fitsSystemWindows="true,这样状态栏就和根布局相同颜色,通过把跟布局颜色设置为ToolBar相同即可实现目的。但这种方式实现有些问题,例如我们想设置状态栏为蓝色,只能通过设置最外层布局的背景为蓝色来实现,然而一旦设置后,整个布局就都变成了蓝色,只能在下方的布局内容里另外再设置白色背景,而这样就存在过度绘制了。所以不建议这样使用!

2.1.2 布局里添加占位状态栏

在根布局加入一个占位状态栏,这样虽然整个内容页面时顶到头的,但是因为在内容布局里添加了一个占位状态栏,所以效果与设想的一致.

<View android:id="@+id/statusBarView" android:background="@color/blue" 设置状态栏颜色 android:layout_width="match_parent" android:layout_height="wrap_content">  设置状态栏高度
  </View>

2.2代码实现

2.2.1 代码设置fitsSystemWindows 属性
/** * 设置页面最外层布局 FitsSystemWindows 属性 * @param activity * @param value */
public static void setFitsSystemWindows(Activity activity, boolean value) {
  ViewGroup contentFrameLayout = (ViewGroup) activity.findViewById(android.R.id.content);
  View parentView = contentFrameLayout.getChildAt(0);
  if (parentView != null && Build.VERSION.SDK_INT >= 14) {
      parentView.setFitsSystemWindows(value);
  }
}

此处只是介绍代码设置fitsSystemWindows 属性的方法,设置过fitsSystemWindows 属性之后还是要设置根布局的颜色才能达到目的。

2.2.2 布局里添加占位状态栏

通过反射获取状态栏高度:

/** * 利用反射获取状态栏高度 * @return */
public int getStatusBarHeight() {
  int result = 0;
  //获取状态栏高度的资源id
  int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
  if (resourceId > 0) {
      result = getResources().getDimensionPixelSize(resourceId);
  }
  return result;
}

代码添加状态栏占位视图:

/** * 添加状态栏占位视图 * * @param activity */
private void addStatusViewWithColor(Activity activity, int color) {
    ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();    
    View statusBarView = new View(activity);
    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            getStatusBarHeight(activity));
    statusBarView.setBackgroundColor(color);
    decorView.addView(statusBarView, lp);
    //decorViewDecorView为整个Window界面的最顶层View, 里面是包含了我们的android.R.id.content的,而且也是个**帧布局**。因为是帧布局,所以我们要调用setFitsSystemWindows设置fitsSystemWindows 属性,添加的view才有用!!
    setFitsSystemWindows(activity,true);
}

关于DecorView和android.R.id.content,可参照https://www.jianshu.com/p/579e9cc8b83c
https://www.jianshu.com/p/ee7e3b08c23c
在这里插入图片描述

Android4.4(API 19)和 Android 5.0(API 21)区别

安卓4.4的用法在安卓5.0之后也完全适用,但是安卓5.0之后不是完全透明状态栏,而是半透明状态栏!
在这里插入图片描述
要想使安卓4.4的设置在安卓5.0以上也适用,加上以下代码:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    Window window = getWindow();            
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.setStatusBarColor(Color.TRANSPARENT);            
    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    }

从后文我们会得知,这里除去了android:fitsSystemWindows="true的作用,而加入了android:statusBarColor = "@android:color/transparent(上面是用代码实现的)。因为这两个属性是不能同时生效的。(android:statusBarColor = "@android:color/transparent是安卓5.0之后提供的属性)。

但是由于android:windowTranslucentStatus属性的禁用,状态栏将不再会是浮在我们的window上。没关系,我们可以通过下面的方法达到一样的效果:

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

其实设置android:windowTranslucentStatus属性时,正是系统帮我们设置了上面的Flag;

上面我们在DecorView上调用这个方法,但其实可以在任何一个可见的View上进行调用,效果是一样的。下面再补充说明一下setSystemUiVisibility其他可用的标志:

View.SYSTEM_UI_FLAG_VISIBLE Level 14  默认标记

View.SYSTEM_UI_FLAG_LOW_PROFILE Level 14
低功耗模式, 会隐藏状态栏图标, 在4.0上可以实现全屏。status bar和navigation bar的相关图标会被弱化,比如navigation bar的几个虚拟键会弱化成很细微的小点。一旦你再次点击 status bar和navigation bar 的所在区域,他们就会再次完全显现。这种方式的好处是status bar和navigation bar并没有消失,仍然在界面上,但是它们的细节变暗了、模糊了。


View.SYSTEM_UI_FLAG_LAYOUT_STABLE Level 16
保持整个View稳定, 常跟bar 悬浮, 隐藏共用, 使View不会因为SystemUI的变化而做layout


View.SYSTEM_UI_FLAG_FULLSCREEN Level 16
状态栏隐藏(导航栏仍然显示)。跟WindowManager.LayoutParams.FLAG_FULLSCREEN有相同的效果(其实不同,因为该标志下statusbar的高度还是会存在,不算真正意义上的全屏),同时在使用ActionBar的FEATURE_ACTION_BAR_OVERLAY时,启用SYSTEM_UI_FLAG_FULLSCREEN 会将ActionBar隐藏;该标志一般适用于短期的全屏状态而不是长期。


View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN Level 16
状态栏上浮于Activity


View.SYSTEM_UI_FLAG_HIDE_NAVIGATION Level 14
暂时隐藏导航栏, 由于导航栏的重要性,当产生细微的用户交互后,比如单击屏幕,都可能会导致navigation bar重新出现,源于系统clear掉该标志与SYSTEM_UI_FLAG_FULLSCREEN 标志,同SYSTEM_UI_FLAG_IMMERSIVE 标志一起使用可避免被clear


View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION Level 16
导航栏上浮于Activity


View.SYSTEM_UI_FLAG_IMMERSIVE Level 19
Kitkat新加入的Flag, 沉浸模式, 跟SYSTEM_UI_FLAG_HIDE_NAVIGATION一起使用才有意义,可以避免系统在产生细微用户交互时系统clear掉SYSTEM_UI_FLAG_HIDE_NAVIGATION标志。如单独使用SYSTEM_UI_FLAG_HIDE_NAVIGATION标志时只需单击屏幕,导航栏就会重新出现;如果同时使用该标志,则不会出现,但用户在导航栏区域仍然可以主动呼出。呼出后,对应的标志会被清除。


View.SYSTEM_UI_FLAG_IMMERSIVE_STIKY Level 19
需要跟SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 或者 SYSTEM_UI_FLAG_FULLSCREEN 一起使用。当单独使用上面的标志时,任何用户交互会导致导航栏重新出现,从顶部向下滑动会重新呼出状态栏,这些操作会导致这些标志被clear掉。如果同时指定SYSTEM_UI_FLAG_IMMERSIVE_STIKY 标志,那么对应标志将不会被清除,且呼出隐藏的bar后会自动再隐藏掉

Android 5.0(API 21)

安卓5.0之后,谷歌直接提供了android:statusBarColor属性,可以直接设置状态栏的颜色。

1.图片沉浸

代码实现为:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); \\设置颜色之前要设置这个flag!且不能有下面那个flag,才能设置setStatusBarColor
    window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    window.setStatusBarColor(Color.TRANSPARENT);//也可在xml文件中实现
    //由于window.setStatusBarColor(Color.TRANSPARENT)并不能使状态栏消失,所以必须设置下面这行代码,使布局充满到状态栏中。
    window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}

这段代码和上文类似。

2.ToolBar沉浸

1、调用window.setStatusBarColor(color),设置状态栏颜色
2、在xml文件中新建一个style,设置android:statusBarColor属性,改变状态栏颜色。

安卓6.0

安卓6.0之后支持根据状态栏的颜色深浅控制状态栏上的字体的颜色。当状态栏为浅色时,字体为深色视觉效果更好。

1、xml实现

<item name="android:windowLightStatusBar">true</item>

设置浅色的状态栏,字体自然会变成深色。

2、代码实现

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {             
	getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}

总结

本文对状态栏的各种状态用xml和代码的方式进行了实现,建议只用代码实现,可以对代码进行包装,可重复利用。程序员从不重复造轮子,同样沉浸式状态栏也已经有轮子,可参考StatusBarUtil
第一篇博客,finish!

自己写的工具类

引入别人的包效果不是很好,所以自己写了一个工具。仅供参考。

import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.v4.graphics.ColorUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;

public class MyStatusBarUtil {

    public static void setTransparent(Activity activity){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.setStatusBarColor(Color.TRANSPARENT);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    public static void setColor(Activity activity,int color){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            activity.getWindow().setStatusBarColor(color);
            if(isLightColor(color))
                setDarkFont(activity);
        }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
            addStatusViewWithColor(activity,color);
            if(isLightColor(color))
                setDarkFont(activity);
        }
    }

    public static void setDarkFont(Activity activity){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }

    }

    /** * 利用反射获取状态栏高度 * @return */
    public static int getStatusBarHeight(Activity activity) {
        int result = 0;
        //获取状态栏高度的资源id
        int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = activity.getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    public static void addStatusViewWithColor(Activity activity, int color) {
        ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
        View statusBarView = new View(activity);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                getStatusBarHeight(activity));
        statusBarView.setBackgroundColor(color);
        decorView.addView(statusBarView, lp);
        //decorViewDecorView为整个Window界面的最顶层View, 里面是包含了我们的android.R.id.content的,而且也是个**帧布局**。因为是帧布局,所以我们要调用setFitsSystemWindows设置fitsSystemWindows 属性,添加的view才有用!!
        setFitsSystemWindows(activity,true);
    }

    /** * 设置页面最外层布局 FitsSystemWindows 属性 * @param activity * @param value */
    public static void setFitsSystemWindows(Activity activity, boolean value) {
        ViewGroup contentFrameLayout = (ViewGroup) activity.findViewById(android.R.id.content);
        View parentView = contentFrameLayout.getChildAt(0);
        if (parentView != null && Build.VERSION.SDK_INT >= 14) {
            parentView.setFitsSystemWindows(value);
        }
    }

    /** * 判断颜色是不是亮色 * * @param color * @return * @from https://stackoverflow.com/questions/24260853/check-if-color-is-dark-or-light-in-android */
    public static boolean isLightColor(@ColorInt int color) {
        return ColorUtils.calculateLuminance(color) >= 0.5;
    }
}