最近在App上需要对状态栏进行相关的设置,在网上看了些文章,像郭神的沉浸式那篇博客对我启发蛮大,但是对状态栏的设置,包括隐藏,透明,颜色设置等,并没有比较系统的概念,实现方式不止一种,有操作Window属性的方式,也有操作decorView.setSystemUiVisibility方法来控制系统UI的方式。但是这些方式有什么区别,以及如何具体操作,所以写这篇博客总结学习一下。

背景

首先要说的一点就是Android系统到了4.4以后才提供沉浸式体验的支持。当设置透明效果后,4.4以下无效果,4.4~5.0全透明,5.0以上半透明;Android沉浸式模式的本质就是全屏化。下面就围绕这个展开学习。

隐藏ActionBar(顶部导航栏/标题栏)

根据Android的设计建议,ActionBar是不应该独立于状态栏而单独显示的,因此状态栏如果隐藏了,我们同时也需要隐藏ActionBar。所以先来了解下隐藏系统标题栏的方法。

这里提供三种方式:
1、调用ActionBar的hide()方法将ActionBar也进行隐藏。

ActionBar actionBar = getSupportActionBar();
actionBar.hide();

2、调用requestWindowFeature(Window.FEATURE_NO_TITLE);(推荐)
注意:supportRequestWindowFeature(Window.FEATURE_NO_TITLE);来实现,不过记得要放在setContentView()方法前面;
3、在主题中设置

< item name="windowNoTitle">true< /item >

注意:”android:windowNoTitle”和“windowNoTitle”两个属性的区别,我使用了V7兼容包主题,有两个所以使用的是后者,这两个属性的区别,因为涉及到内容较多,所以单独再写了一篇博客。

设置状态栏和导航栏透明的方式

这里提供三种方式:

1、主题方式
可以用在< appliction >节点下的主题,也可以根据项目需求放在相应的< Activity>节点下的主题上;这种方式兼顾到Android本身状态栏对于不同版本的适配(例如4.4~5.0全透明,5.0以上半透明),会根据不同版本来适配。

<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<!--Android 5.x开始需要把颜色设置透明,否则导航栏会呈现系统默认的浅灰色-->
<item name="android:statusBarColor">@android:color/transparent</item>

2、通过系统提供的标志位设置(推荐)
这种方式和方式一同样会根据Android本身状态栏对于不同版本来适配

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//设置透明状态栏
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//设置透明导航栏
}

3、通过给系统窗口设置颜色值
这种方式可以避免Android本身状态栏对于不同版本的适配,只要用这种方式设置,5.0以上也可以实现全透明。但前提要求是Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP,只能适用于5.0以上的系统。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
    getWindow().setStatusBarColor(Color.TRANSPARENT);//将状态栏设置成透明色
    getWindow().setNavigationBarColor(Color.TRANSPARENT);//将导航栏设置为透明色
}

状态栏透明

这种需求一般容易出现在顶部直接就是图片的情况,如果是标题栏的话,直接设置透明会让文本内容置于状态栏下面,不利于一些点击事件的响应。

效果如下:

android 透明底部导航栏 安卓透明导航栏_状态栏

代码的话直接按上面透明方式设置就可以了

状态栏和顶部的控件保持同一个颜色

开发中大部分需求是实现状态栏和顶部的控件是同一个颜色,同时,控件内容也不和状态栏重复。要实现这样的效果关键是在xml中给顶部控件添加上两个属性就可以了,这两个属性在我博客Android中XML属性中都有详细的文章。这里就不多赘述了。

android:fitsSystemWindows="true"  
android:clipToPadding="true"

先看一下效果图:

android 透明底部导航栏 安卓透明导航栏_android 透明底部导航栏_02

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:id="@+id/llt_root"
              android:orientation="vertical">
    <TextView
        android:fitsSystemWindows="true"
        android:clipToPadding="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="App标题栏"
        android:textSize="30sp"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#0CE3EE"
        android:gravity="center"
        android:text="App内容部分"
        android:textSize="30sp"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#4188F8"
        android:gravity="center"
        android:text="App导航栏"
        android:textSize="30sp"/>
</LinearLayout>

然后设置好状态栏透明:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= 21) {  
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            getWindow().setStatusBarColor(Color.TRANSPARENT);//防止5.x以后半透明影响效果,使用这种透明方式
         }

    }

隐藏状态栏和导航栏(沉浸式体验)

界面默认情况下是全屏的,状态栏和导航栏都不会显示。而当我们需要用到状态栏或导航栏时,只需要在屏幕顶部向下拉,状态栏和导航栏就会显示出来,此时界面上任何元素的显示或大小都不会受影响。过一段时间后如果没有任何操作,状态栏和导航栏又会自动隐藏起来,重新回到全屏状态。

效果如图:

android 透明底部导航栏 安卓透明导航栏_android 透明底部导航栏_03


这里附上一张郭霖博客中的图片,该博客地址在总结中给出

当你确定要使用沉浸式模式,那么只需要重写Activity的onWindowFocusChanged()方法,然后加入如下逻辑即可:

//沉浸式体验
@Override
public void onWindowFocusChanged(boolean hasFocus) {
   super.onWindowFocusChanged(hasFocus);
   if (hasFocus && Build.VERSION.SDK_INT >= 19) {
       View decorView = getWindow().getDecorView();
       decorView.setSystemUiVisibility(
               View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                       | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                       | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                       | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                       | View.SYSTEM_UI_FLAG_FULLSCREEN
                       | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
       getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//设置透明状态栏
       getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//设置透明导航栏
   }

整理成工具类

将以上概念整理成工具类,直接拷贝使用就可以了。

public class BaseApp extends Application {

    public static float mDensity;
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
        initScreenSize();
    }

    private static void initScreenSize() {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        mDensity = dm.density;
    }

    //=============沉侵式==(begin)=================
    private static View mStatusBarView;

    /** 设置全屏沉侵式效果 */
    public static void setNoStatusBarFullMode(Activity activity) {
        // sdk 4.4
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
            Window window = activity.getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);

            if (mStatusBarView != null) {
                ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
                root.removeView(mStatusBarView);
            }
            return;
        }

        // sdk 5.x
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            window.setStatusBarColor(Color.TRANSPARENT);
            return;
        }
    }

    /** 设置控件的paddingTop, 使它不被StatusBar覆盖 */
    public static void setStatusBarPadding(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            int marginTop = getStatusBarHeight(view.getContext());
            view.setPadding(view.getPaddingLeft(), marginTop,
                    view.getPaddingRight(), view.getPaddingBottom());
            return;
        }
    }


    public static void setStatusBarColor(Activity activity, int statusColor) {
        // sdk 4.4
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
                if (mStatusBarView == null) {
                   //为了适配一些特殊机型的状态栏颜色无法改变,同时高度和系统原生的高度区别,所以这里重新创建一个View用于覆盖状态栏来实现效果
                    mStatusBarView = new View(activity);
                    mStatusBarView.setBackgroundColor(statusColor);
                } else {
                    // 先解除父子控件关系,否则重复把一个控件多次
                    // 添加到其它父控件中会出错
                    ViewParent parent = mStatusBarView.getParent();
                    if (parent != null) {
                        ViewGroup viewGroup = (ViewGroup) parent;
                        if (viewGroup != null)
                            viewGroup.removeView(mStatusBarView);
                    }
                }
                ViewGroup.LayoutParams param = new ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        getStatusBarHeight(activity));
                root.addView(mStatusBarView, param);
            }
            return;
        }

        // sdk 5.x
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            activity.getWindow().setStatusBarColor(statusColor);
            return;
        }
    }

    /**
     * 通过反射的方式获取状态栏高度,
     * 一般为24dp,有些可能较特殊,所以需要反射动态获取
     */
    private static int getStatusBarHeight(Context context) {
        try {
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            Object obj = clazz.newInstance();
            Field field = clazz.getField("status_bar_height");
            int id = Integer.parseInt(field.get(obj).toString());
            return context.getResources().getDimensionPixelSize(id);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("-------无法获取到状态栏高度");
        }
        return dp2px(24);
    }

    public static int dp2px(int dp) {
        return (int) (dp * mDensity);
    }
    //=============沉侵式==(end)=================

总结

这里要说的是,其实真正的沉浸式体验除了像游戏或者视频软件这类特殊的应用,大多数的应用程序都是用不到沉浸式模式的。大部分情况下,只是要求状态栏能和所在页面顶部View的颜色保持一致,且不覆盖该View的内容。
也许在一些手机上还会遇到适配不成功的问题,因为国内手机生产商都会对Android手机系统做一定的定制,当然这也是Android碎片化的一个影响嘛。但是这不是我们做技术的借口,遇到难题还是要有去解决的激情。当然如果博文中有什么错误的地方,希望能及时留言帮我指出,这样才能不断的进步嘛。