由于安卓系统的差异,以及碎片化的日益严重导致一个状态栏的控制要花费大量的功夫进行适配。状态栏导航栏算是在开发中比较常见的,但是一直都没有完全的搞懂,总是遇到一个问题解决一个问题,今天把这些知识点罗列在一起,不说完全搞懂状态栏这个东西,起码在以后用到的时候不会出现解决不了的问题。
这篇文章首先会对系统各个版本控制状态栏的api以及实现的效果做介绍,然后对各个flag进行分析,最后对fitsSystemWindows这个属性进行详解,因为这个属性迷惑了我好久。
android的状态栏大致经历了以下几个阶段:
前面已经说过了android4.4—android5.0主要通过FLAG_TRANSLUCENT_STATUS这个属性实现状态栏变色,当使用这个flag时SYSTEM_UI_FLAG_LAYOUT_STABLE和SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN会被自动添加。
这个属性怎样添加呢?有两种方式:
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- true
布局代码:
具体效果:
我在上面的图片中加了一个textview,可以看到这个textview绘制到了顶部的状态栏中,还记得之前说的方法吗,这时就需要添加一个view填充在状态栏上,view的高度就是状态栏的高度,颜色就是你想要的状态栏的颜色。大致的代码如下:
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
int count = decorView.getChildCount();
//判断是否已经添加了statusBarView
if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
} else {
//新建一个和状态栏高宽的view
StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha);
decorView.addView(statusView);
}
ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
//rootview不会为状态栏留出状态栏空间
ViewCompat.setFitsSystemWindows(rootView,true);
rootView.setClipToPadding(true);
private static StatusBarView createStatusBarView(Activity activity, int color, int alpha) {
// 绘制一个和状态栏一样高的矩形
StatusBarView statusBarView = new StatusBarView(activity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity));
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(calculateStatusColor(color, alpha));
return statusBarView;
}
在开始之前首先看一张图片:
上面的那些颜色都是可以在主题中设置的,如果没有动态改变他们会一直遵循这个原则。
android5.0是android的一次重大更新,很多api都是在这个版本上添加的(真想最低适配android5.0),setStatusBarColor是专门用来设置状态栏颜色的,但是让这个方法生效有一个前提条件:你必须给window添加FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS并且取消FLAG_TRANSLUCENT_STATUS,前面已经说过了FLAG_TRANSLUCENT_STATUS会让状态栏透明,那FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS这个属性又是干嘛的呢?
解释:设置了FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,表明会Window负责系统bar的background 绘制,绘制透明背景的系统bar(状态栏和导航栏),然后用getStatusBarColor()和getNavigationBarColor()的颜色填充相应的区域。这就是Android 5.0 以上实现沉浸式导航栏的原理。
添加方式也有两种:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().setStatusBarColor(ContextCompat.getColor(this,android.R.color.holo_blue_dark));
- false
- true
- @android:color/holo_blue_dark
具体效果:
可以看到状态栏的颜色和我们设置的一样,并且布局内容也自动移到了状态栏下方。
使用沉浸式的时候会遇到一个问题,那就是Android 系统状态栏的字色和图标颜色为白色,当我的主题色或者图片接近白色或者为浅色的时候,状态栏上的内容就看不清了。 ,这个问题在Android 6.0的时候得到了解决。Android 6.0 新添加了一个属性SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
解释:为setSystemUiVisibility(int)方法添加的Flag,请求status bar 绘制模式,它可以兼容亮色背景的status bar 。要在设置了FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag ,同时清除了FLAG_TRANSLUCENT_STATUS flag 才会生效。
添加方式同样是两种:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); }
- true
这个属性可能很多人没有用过,但是他对于状态栏的控制是非常重要的,这个方法主要是用来设置系统ui的可见性以及和用户布局的位置关系。使用方式如下:
getWindow().getDecorView().setSystemUiVisibility(flag);
下面着重介绍一下flag:
终于写到坑我最深的东西了,这个属性相信所有人都用过,也都知道他的作用,但是总是碰到和期望不一样的效果,这个属性的作用就是**通过设置View的padding,使得应用的content部分——Activity中setContentView()中传入的就是content——不会与system window重叠。**也就是让内容布局向下调整一段距离。
还有一些事情需要注意:
到这里是不是有一个疑问,在使用系统提供的一些控件时fitsSystemWindows 这个属性有时候并不仅仅是设置给最外层的根布局,他的子view也会添加这个属性,不是说这个属性只对根view才有效吗?其实这句话本身没问题,但是他的的前提是没有对view进行重写,在KITKAT及以下的版本,你的自定义View能够通过覆盖fitsSystemWindows() : boolean函数,来增加自定义行为。如果返回true,意味着你已经占据了整个Insets,如果返回false,意味着给其他的View依旧留有机会。所以当我们返回false时子view的fitsSystemWindows 就有效。(举个例子:CoordinatorLayout嵌套AppBarLayout嵌套CollapsingToolbarLayout嵌套ImageView,这是一个常见的效果,具体是什么我就不多介绍了,这里的每一个view都需要设置fitsSystemWindows为true,这样事件才能传到 ImageView中)
在Lollipop以及更高的版本,我们提供了一些新的API,使得自定义这个行为更加的方便。与之前的版本不同,现在你只需要覆盖OnApplyWindowInsets()方法,该方法允许View消耗它想消耗的任意空间(Insets),同时也能够为子方法,调用dispatchApplyWindowInsets()
更妙的是,利用新的API,你甚至不需要拓展View类,你可以使用ViewCompat.setOnApplyWindowInsetsListener(),这个方法优先于View.onApplyWindowInsets()调用。ViewCompat 同时也提供了 onApplyWindowInsets() 和dispatchApplyWindowInsets()的兼容版本,无需冗长的版本判断。
flContent.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
return clContent.dispatchApplyWindowInsets(insets);
}
});
上面的代码将flContent的fitsSystemWindows事件传递给clContent处理。
关于fitsSystemWindows的详细介绍
补充
看到评论里有人提出光讲了差异,没有具体的适配代码,这里推荐一个适配库StatusBarUtil