最近在研究安卓沉浸式状态栏,看了很多国内的博客,说的都很乱,在此就自己的心得进行总结。本文将根据安卓版本的不同,实现方式不同(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之前,我们的应用没法改变手机的状态栏颜色,当我们打开应用时,会出现上图中左侧的画面,在屏幕的顶部有一条黑色的状态栏,和应用的风格非常不协调。
安卓4.4之后支持沉浸式状态栏,但是和安卓5.0之后有点区别。
<style name="NewStyle" parent="Theme.AppCompat.Light.NoActionBar">
- "android:windowTranslucentStatus"
>true 系统状态栏透明
- "android:windowTranslucentNavigation">true
系统导航栏透明,不在本文范围内,可以不加。
style>
这会导致布局充满到状态栏和导航栏中!如图左:
如果不希望图片沉浸入状态栏,可以在根布局加入 android:fitsSystemWindows="true"
此属性时使布局时考虑系统布局的位置(状态栏、导航栏等),所以不会遮挡状态栏和导航栏。状态栏和导航栏会和根布局相同颜色,此时也失去了将图片沉浸的意义,这种方式适合含有toolbar时。
/**
* 设置状态栏颜色
*
* @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);
由上面可知,图片沉浸到状态栏中。但有时候,比如含有ToolBar时,我们并不想把Toolbar布局沉浸到状态栏中,因为这会使状态栏压盖ToolBar,如图左。我们希望ToolBar和状态栏颜色相同,如图右。
在根布局上设置android:fitsSystemWindows="true,这样状态栏就和根布局相同颜色,通过把跟布局颜色设置为ToolBar相同即可实现目的。但这种方式实现有些问题,例如我们想设置状态栏为蓝色,只能通过设置最外层布局的背景为蓝色来实现,然而一旦设置后,整个布局就都变成了蓝色,只能在下方的布局内容里另外再设置白色背景,而这样就存在过度绘制了。所以不建议这样使用!
在根布局加入一个占位状态栏,这样虽然整个内容页面时顶到头的,但是因为在内容布局里添加了一个占位状态栏,所以效果与设想的一致.
<View
android:id="@+id/statusBarView"
android:background="@color/blue" 设置状态栏颜色
android:layout_width="match_parent"
android:layout_height="wrap_content"> 设置状态栏高度
View>
/**
* 设置页面最外层布局 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 属性之后还是要设置根布局的颜色才能达到目的。
通过反射获取状态栏高度:
/**
* 利用反射获取状态栏高度
* @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
安卓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后会自动再隐藏掉
安卓5.0之后,谷歌直接提供了android:statusBarColor属性,可以直接设置状态栏的颜色。
代码实现为:
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);
}
这段代码和上文类似。
1、调用window.setStatusBarColor(color),设置状态栏颜色
2、在xml文件中新建一个style,设置android:statusBarColor属性,改变状态栏颜色。
安卓6.0之后支持根据状态栏的颜色深浅控制状态栏上的字体的颜色。当状态栏为浅色时,字体为深色视觉效果更好。
1、xml实现
- true
设置浅色的状态栏,字体自然会变成深色。
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;
}
}