Android状态栏详解(全网最详)

一、前言

由于安卓系统的差异,以及碎片化的日益严重导致一个状态栏的控制要花费大量的功夫进行适配。状态栏导航栏算是在开发中比较常见的,但是一直都没有完全的搞懂,总是遇到一个问题解决一个问题,今天把这些知识点罗列在一起,不说完全搞懂状态栏这个东西,起码在以后用到的时候不会出现解决不了的问题。
这篇文章首先会对系统各个版本控制状态栏的api以及实现的效果做介绍,然后对各个flag进行分析,最后对fitsSystemWindows这个属性进行详解,因为这个属性迷惑了我好久。

二、状态栏

android的状态栏大致经历了以下几个阶段:

  • 在android4.4以下就不要想着对状态栏做什么文章了,现在app的适配一般也是在android4.4以上了。
  • 在android4.4—android5.0可以实现状态栏的变色,但是效果还不是很好,主要实现方式是通过FLAG_TRANSLUCENT_STATUS这个属性设置状态栏为透明并且为全屏模式,然后通过添加一个与StatusBar 一样大小的View,将View 设置为我们想要的颜色,从而来实现状态栏变色。
  • 在android5.0—android6.0系统才真正的支持状态栏变色,系统加入了一个重要的属性和方法 android:statusBarColor (对应方法为 setStatusBarColor),通过这个属性可以直接设置状态栏的颜色。
  • 在android6.0上主要就是添加了一个功能可以修改状态栏上内容和图标的颜色(黑色和白色)

1. android4.4—android5.0

前面已经说过了android4.4—android5.0主要通过FLAG_TRANSLUCENT_STATUS这个属性实现状态栏变色,当使用这个flag时SYSTEM_UI_FLAG_LAYOUT_STABLESYSTEM_UI_FLAG_LAYOUT_FULLSCREEN会被自动添加。

  • 对于4.4的机型,小米和魅族是透明色,而其它系统上就只是黑色到透明色的渐变。
  • 对于5.x的机型,大部分是使背景色半透明,小米和魅族以及其它少数机型会全透明
  • 对于6.0以上的机型,设置此flage会使得StatusBar完全透明
  • 对于7.0以上的机型,设置此flage会使得StatusBar半透明

这个属性怎样添加呢?有两种方式:

  1. 在代码中设置:activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
  2. 在主题中设置:<item name="android:windowTranslucentStatus">true</item>

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:background="@android:color/holo_red_dark"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            android:textColor="@android:color/white" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

具体效果:
在这里插入图片描述
我在上面的图片中加了一个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;
    }

2. android5.0—android6.0

在开始之前首先看一张图片:
在这里插入图片描述
上面的那些颜色都是可以在主题中设置的,如果没有动态改变他们会一直遵循这个原则。
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 以上实现沉浸式导航栏的原理。

添加方式也有两种:

  1. 在代码中设置: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));
  2. 在布局文件中设置(因为是android5.0新添加的属性,所以在添加到values-v21文件夹下的主题中):<item name="android:windowTranslucentStatus">false</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:statusBarColor">@android:color/holo_blue_dark</item>

具体效果:
在这里插入图片描述
可以看到状态栏的颜色和我们设置的一样,并且布局内容也自动移到了状态栏下方。

3. android6.0

使用沉浸式的时候会遇到一个问题,那就是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 才会生效。

添加方式同样是两种:

  1. 在代码中设置: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); }
  2. 在布局文件中设置(因为是android5.0新添加的属性,所以在添加到values-v23文件夹下的主题中):<item name="android:windowLightStatusBar">true</item>

三、setSystemUiVisibility

这个属性可能很多人没有用过,但是他对于状态栏的控制是非常重要的,这个方法主要是用来设置系统ui的可见性以及和用户布局的位置关系。使用方式如下:

getWindow().getDecorView().setSystemUiVisibility(flag);

下面着重介绍一下flag:

  • SYSTEM_UI_FLAG_FULLSCREEN(4.1+):隐藏状态栏,手指在屏幕顶部往下拖动,状态栏会再次出现且不会消失,另外activity界面会重新调整大小,直观感觉就是activity高度有个变小的过程。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(4.1+):状态栏一直存在并且不会挤压activity高度,状态栏会覆盖在activity之上
    在这里插入图片描述
  • SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(4.1+):配合SYSTEM_UI_FLAG_FULLSCREEN一起使用,效果使得状态栏出现的时候不会挤压activity高度,状态栏会覆盖在activity之上
    在这里插入图片描述
  • SYSTEM_UI_FLAG_HIDE_NAVIGATION(4.0+):会使得虚拟导航栏隐藏,但是由于NavigationBar是非常重要的,因此只要有用户交互(例如点击一个 button),系统就会清除这个flag使NavigationBar就会再次出现,同时activity界面会被挤压 。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION(4.1+):效果使得导航栏不会挤压activity高度,导航栏会覆盖在activity之上。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION(4.1+):配合 SYSTEM_UI_FLAG_HIDE_NAVIGATION 一起使用,效果使得导航栏出现的时候不会挤压activity高度,导航栏会覆盖在activity之上。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_IMMERSIVE配合SYSTEM_UI_FLAG_HIDE_NAVIGATION一起使用,还记得之前使用SYSTEM_UI_FLAG_HIDE_NAVIGATION之后只要有用户交互,系统就会清除这个flag使NavigationBar就会再次出现,和SYSTEM_UI_FLAG_IMMERSIVE一起使用之后就会使SYSTEM_UI_FLAG_HIDE_NAVIGATION必须手指在屏幕底部往上拖动NavigationBar才会出现。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_IMMERSIVE_STICKY配合View.SYSTEM_UI_FLAG_FULLSCREEN和View.SYSTEM_UI_FLAG_HIDE_NAVIGATION一起使用,会使状态栏和导航栏以透明的形式出现,并且一段时间后自动消失。
    在这里插入图片描述
  • SYSTEM_UI_FLAG_LAYOUT_STABLE:稳定布局,主要是在全屏和非全屏切换时,布局不要有大的变化。一般和View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN、View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION搭配使用。(其实具体有什么用我也没搞懂)

四、fitsystemwindow

终于写到坑我最深的东西了,这个属性相信所有人都用过,也都知道他的作用,但是总是碰到和期望不一样的效果,这个属性的作用就是**通过设置View的padding,使得应用的content部分——Activity中setContentView()中传入的就是content——不会与system window重叠。**也就是让内容布局向下调整一段距离。
还有一些事情需要注意:

  • fitsSystemWindows 需要被设置给根View——这个属性可以被设置给任意View,但是只有根View(content部分的根)外面才是SystemWindow,所以只有设置给根View才有用。
  • 其它padding将通通被覆盖。需要注意,如果你对一个View设置了android:fitsSystemWindows=“true”,那么你对该View设置的其他padding将通通无效。

到这里是不是有一个疑问,在使用系统提供的一些控件时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的详细介绍

发布了65 篇原创文章 · 获赞 24 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/shanshui911587154/article/details/86623646