前言:
工作几年下来,处理statusbar的机会和场景不算多,每次遇到具体业务问题时,都是去网上找一些api解决当前问题,但是从来没有从根本上去了解statusbar的发展历程,今天下定决心去彻底认识statusbar。
了解statusbar的发展需要分三个阶段:
- Android4.4(API19 KitKat)以前:无法做任何事,是的,就是一坨黑色。
- Android4.4~Android5.0(API21 Lollipop):可以实现状态栏的变色,但是效果还不是很好,主要实现方式是通过FLAG_TRANSLUCENT_STATUS这个属性设置状态栏为透明并且为全屏模式,然后通过添加一个与StatusBar 一样大小的View,将View 设置为我们想要的颜色,从而来实现状态栏变色。
- Android5.0~Android6.0(API23 Marshmallow): 系统才真正的支持状态栏变色,系统加入了一个重要的属性和方法 android:statusBarColor (对应方法为 setStatusBarColor),通过这个属性可以直接设置状态栏的颜色
- Android6.0以后:主要就是添加了一个功能可以修改状态栏上内容和图标的颜色(黑色和白色)
下面开始了解主要的API:
一、首先新建一个项目,注意把values/values-v19/values-v21中凡是在style.xml中 有关 windowTranslucentNavigation、windowTranslucentStatus、statusBarColor 都删掉。 同时把布局带有android:fitsSystemWindows注释掉。
public class Main3Activity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main3Activity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@android:color/holo_red_light"
android:text="测试文案"
/>
<View
android:id="@+id/testview"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@color/colorPrimary"
tools:ignore="MissingConstraints" />
</LinearLayout>
跑出来的效果是:
从左到右分别是 API 19(4.4), 21(5.0), 29(9.+)
二、代码中设置FLAG_TRANSLUCENT_STATUS后,请看如下效果:
public class Main3Activity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
4.4会全透明,页面上顶。
模拟器4.4应该是有bug,看不出效果,建议使用真机
4.4以上是一样的效果,页面整体上顶,沉在状态栏之下,同时状态栏变为透明!
下图左边是 api21, 右边是api29, 以下相同
这里我们给出一个结论:
android4.4—android5.0主要通过FLAG_TRANSLUCENT_STATUS这个属性实现状态栏变色,当使用这个flag时SYSTEM_UI_FLAG_LAYOUT_STABLE和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN会被自动添加。
这显然不是我们想要的效果,我们试着让状态栏透明,同时页面不被状态栏遮挡
三、我们使用setFitsSystemWindows(android:fitsSystemWindows=“true”)这个api试试。
public class Main3Activity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
ViewGroup rootView = (ViewGroup) ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
rootView.setFitsSystemWindows(true);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true" //注意是这里!
tools:context=".Main3Activity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@android:color/holo_red_light"
android:text="测试文案"
/>
<View
android:id="@+id/testview"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@color/colorPrimary"
tools:ignore="MissingConstraints" />
</LinearLayout>
或者在XML局部中设置:
四、4.4没有提供改变statusbar颜色的方案,我们采取了一个hack的方式。
方法是:要添加一个view填充在状态栏上,view的高度就是状态栏的高度,颜色就是你想要的状态栏的颜色。
public class Main3Activity extends AppCompatActivity{
private static final int FAKE_STATUS_BAR_VIEW_ID = R.id.statusbarutil_fake_status_bar_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
ViewGroup decorView = (ViewGroup) getWindow().getDecorView();
View fakeStatusBarView = decorView.findViewById(R.id.statusbarutil_fake_status_bar_view);
if (fakeStatusBarView != null) {
if (fakeStatusBarView.getVisibility() == View.GONE) {
fakeStatusBarView.setVisibility(View.VISIBLE);
}
fakeStatusBarView.setBackgroundColor(getResources().getColor(R.color.colorAccent));
} else {
decorView.addView(createStatusBarView(getResources().getColor(R.color.colorAccent)));
}
setRootView();
}
}
private void setRootView() {
ViewGroup parent = (ViewGroup) findViewById(android.R.id.content);
for (int i = 0, count = parent.getChildCount(); i < count; i++) {
View childView = parent.getChildAt(i);
if (childView instanceof ViewGroup) {
childView.setFitsSystemWindows(true);
((ViewGroup) childView).setClipToPadding(true);
}
}
}
private View createStatusBarView( @ColorInt int color) {
// 绘制一个和状态栏一样高的矩形
View statusBarView = new View(this);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight());
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(color);
statusBarView.setId(FAKE_STATUS_BAR_VIEW_ID);
return statusBarView;
}
private int getStatusBarHeight() {
// 获得状态栏高度
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
return getResources().getDimensionPixelSize(resourceId);
}
}
注意:以下分析的api都与4.4无关了,4.4能做的事情就是对添加到顶部的view进行UI的变化,自主定制,思路都是一致的。
五、5.0(包含5.0)以上,设置statusbar的颜色可以直接利用一个window提供的api即可,不需要做任何限制。
注意:写改代码时一定要判断sdk版本,不加判断的话,运行到5.0以下机器上会直接crash
public class Main3Activity extends AppCompatActivity{
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
// 上面三行代码,如果设置为全透明,则必须加上,设置为半透明,则可以不需要
getWindow().setStatusBarColor(getResources().getColor(R.color.colorAccent));
}
}
}
六、如果实现导航栏半透明,图片作为布局底部,沉在导航栏下的效果。
public class Main3Activity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置状态栏透明
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main3Activity">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:src="@drawable/brand_pic_kr"
android:scaleType="centerCrop"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@android:color/holo_red_light"
android:text="测试文案"
/>
<View
android:id="@+id/testview"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@color/colorPrimary"
tools:ignore="MissingConstraints" />
</LinearLayout>
七、改变状态栏文字颜色:
Android 6.0 以上使用系统方法修改状态栏字体、图标颜色;
Android 4.4 到 6.0 之间使用第三方系统提供的方法修改状态栏字体、图标颜色(目前只有 MIUI 和 Flyme)。
setMIUIStatusBarDarkIcon(activity, true);
setMeizuStatusBarDarkIcon(activity, true);
//设置为黑色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
//设置为白色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
private static void setMIUIStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) {
Class<? extends Window> clazz = activity.getWindow().getClass();
try {
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
int darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
extraFlagField.invoke(activity.getWindow(), darkIcon ? darkModeFlag : 0, darkModeFlag);
} catch (Exception e) {
//e.printStackTrace();
}
}
private static void setMeizuStatusBarDarkIcon(@NonNull Activity activity, boolean darkIcon) {
try {
WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (darkIcon) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
activity.getWindow().setAttributes(lp);
} catch (Exception e) {
//e.printStackTrace();
}
}
API29状态栏文字设置为黑色的效果:
八、FLAG_FULLSCREEN的作用:
public class Main3Activity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
}
分别为api19,21,29
使状态栏消失,比如小说阅读APP中可以使用到。
不得不提一句,除此以外,还有很多个flag,其实很多api功能重叠,或者因为版本问题遗留,导致该问题的处理上没有统一方案,大家找到适合自己的就好。
九、实现类似横屏视频播放的效果,默认不展示状态栏,触摸屏幕时显示出来状态栏。
参考:https://blog.csdn.net/sbsujjbcy/article/details/48391863
十、基本原理弄清楚以后,其实我们在实际使用中,可以推荐一个牛逼的轮子:
https://jaeger.itscoder.com/android/2016/03/27/statusbar-util.html