Android關於沉浸式狀態列總結

依然範特稀西發表於2017-08-08

一、前言

其實我是不打算寫這篇文章的,為什麼呢?因為關於沉浸式狀態列的文章太多了,隨便google一下就能出來幾十上百篇文章,當然這其中有寫的好的,也有濫竽充數的。前面在公眾號推出了Material Design 的系列文章,就有讀者留言,希望出一篇關於沉浸式的文章。因此這篇文章就整理總結一下各個版本的實現原理,順便為大家推薦一個我覺得很方便的一個庫。

二、沉浸式的一般套路

在介紹這個方便的輪子之前,我們先一起來回顧一下實現沉浸式狀態列的一般套路。在Android上,關於對StatusBar(狀態列)的操作,一直都在不斷改善,並且表現越來越好,在Android4.4 以下,我們可以對StatusBar和 NavigationBar進行顯示和隱藏操作。但是直到Android4.4,我們才能真正意義上的實現沉浸式狀態列。從Android4.4 到現在(Android 7.1),關於沉浸式大概可以分成三個階段:

  • 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以下就能實現)

大概就是這個三個階段,那麼接下來我們就看一下這個三個階段分別是如何來實現的。

2.1 Android4.4(API 19) - Android 5.0(API 21)實現沉浸式的方式

Android 4.4 為什麼能夠實現沉浸式的效果呢?因為在Android 4.4 新增了一個重要的屬性:FLAG_TRANSLUCENT_STATUS

 /**
         * Window flag: request a translucent status bar with minimal system-provided
         * background protection.
         *
         * <p>This flag can be controlled in your theme through the
         * {@link android.R.attr#windowTranslucentStatus} attribute; this attribute
         * is automatically set for you in the standard translucent decor themes
         * such as
         * {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
         * {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
         * {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
         * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.</p>
         *
         * <p>When this flag is enabled for a window, it automatically sets
         * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
         * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
         */
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;複製程式碼

解釋:設定狀態列透明,並且變為全屏模式。上面的解釋已經說得很清楚了,當window的這個屬性有效的時候,會自動設定 system ui visibility的標誌SYSTEM_UI_FLAG_LAYOUT_STABLESYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

有兩種方式實現這個屬性:

可以在程式碼中設定,如下:

activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);複製程式碼

當然也可以在theme 中設定屬性windowTranslucentStatus,如下:

android:windowTranslucentStatus複製程式碼

效果如下:

效果如上圖,可以看出,沉浸式的效果是出來了,但是也有一個問題,我們的標題欄和狀態列重疊了,相當於整個佈局上移了StatusBar 的高度。

為了讓標題欄回到原來的位置,我們在標題欄的上方新增一個大小和StatusBar大小一樣的View,View 的BackgroundColor 為標題欄一樣的顏色,這個View起到一個佔位的作用。這個時候,標題欄就會下移StatusBar的高度,回到正常的位置。

新增如下程式碼:

       //獲取windowphone下的decorView
        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);複製程式碼

建立和status bar 一樣大小的View的程式碼如下:

 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;
    }複製程式碼

其中StatusBarView 就是一個普通的View。

新增上述程式碼後,效果如下:

通過以上就可以實現Android 4.4 上的沉浸式狀態列。

另外,如果是一張圖片延伸到狀態列的話,直接設定FLAG_TRANSLUCENT_STATUS就可以了,如下:

小結:Android4.4上實現沉浸式狀態列的套路是:為window新增FLAG_TRANSLUCENT_STATUS Flag,然後新增一個和status bar 一樣大小的View 站位,從而讓讓標題欄不會與status bar 重疊。而圖片延伸到狀態列只需要設定FLAG_TRANSLUCENT_STATUS就OK。

前面說過,沉浸式在Android4.4 - Android5.0 之間的版本表現得不是很好,從上面貼的幾張圖就可以看出,狀態列的頂部有一個漸變,會顯示出黑色的陰影(底部的導航欄也是一樣的效果),在Android 5.0 版本已經被修復了。

2.2 Android 5.0(API 21)以上實現沉浸式的方式

Android 5.0 是一個里程碑式的版本,從Android 5.0開始,Google 推出了全新的設計規範 Material Design,並且原生控制元件就可以實現一些炫酷的UI動效。從這個版本開始,google 加入了一個比較重要的方法setStatusBarColor (對應屬性:android:statusBarColor),通過這個方法,可以很輕鬆地實現沉浸式狀態列。方法如下:

 /**
     * Sets the color of the status bar to {@code color}.
     *
     * For this to take effect,
     * the window must be drawing the system bar backgrounds with
     * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
     * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
     *
     * If {@code color} is not opaque, consider setting
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
     * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
     * <p>
     * The transitionName for the view background will be "android:status:background".
     * </p>
     */
    public abstract void setStatusBarColor(@ColorInt int color);複製程式碼

注意看這個方法的註釋,想要這個方法生效,必須還要配合一個Flag一起使用,必須設定FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS ,並且不能設定FLAG_TRANSLUCENT_STATUS(Android 4.4才用這個)

我們來看一下FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS這個flag:

可以看到,這個flag 也是在Android 5.0新增的,它的作用是什麼呢?

解釋:設定了FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,表明會Window負責系統bar的background 繪製,繪製透明背景的系統bar(狀態列和導航欄),然後用getStatusBarColor()getNavigationBarColor()的顏色填充相應的區域。這就是Android 5.0 以上實現沉浸式導航欄的原理。

實現沉浸式新增如下程式碼:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//注意要清除 FLAG_TRANSLUCENT_STATUS flag
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setStatusBarColor(getResources().getColor(android.R.color.holo_red_light));複製程式碼

效果如下:

當然也可以直接在Theme中使用,在values-v21資料夾下新增如下主題:

<style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">false</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/holo_red_light</item>
    </style>複製程式碼

效果和上面程式碼中新增的效果一樣,這裡就不貼效果圖了。

圖片延伸到狀態列

在Android 5.0 使圖片延伸到狀態列,只需設定windowTranslucentStatus,將 statusBarColor 設定為透明即可:

<style name="ImageTranslucentTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
        <item name="android:windowTranslucentNavigation">true</item>
        <item name="android:windowTranslucentStatus">true</item>
        <!-- 設定statusBarColor 為透明-->
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>複製程式碼

效果如下:

程式碼中通過版本號的判斷相容 Android5.0以下和Android 5.0以上:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha));
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
            int count = decorView.getChildCount();
            if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) {
                decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha));
            } else {
                StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha);
                decorView.addView(statusView);
            }

            ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
            rootView.setFitsSystemWindows(true);
            rootView.setClipToPadding(true);
            setRootView(activity);
}複製程式碼

2.3 Android 6.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_BACKGROUNDSflag ,同時清除了FLAG_TRANSLUCENT_STATUSflag 才會生效。

新增如下程式碼:

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);
}複製程式碼

效果如下:

除了在程式碼中新增以外,還可以直接在主題中使用屬性:

 <style name="MDTheme" parent="Theme.Design.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">false</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/holo_red_light</item>
        <!-- Android 6.0以上 狀態列字色和圖示為淺黑色-->
        <item name="android:windowLightStatusBar">true</item>
    </style>複製程式碼

注意:主題要放在values-v23資料夾下:

三、輪子StatusBarUtil

通過上面的介紹,其實將各個版本實現沉浸式的方式和原理都講完了。但是或許當你真正去實踐沉浸式狀態列的時候,你會感覺到無從下手,因此,我給大家推薦一個輪子StatusBarUtil,Github:github.com/laobie/Stat…

為什麼會推薦這個庫呢?因為這個庫就只有一個類StatusBarUtil,使用起來很方便,就像一個工具類一樣使用。裡面封裝了很多靜態方法,直接使用就好。自己新增也很方便。介紹一下使用的一些場景:

需要在setContentView()之後呼叫:

setContentView(R.layout.main_activity);
...
StatusBarUtil.setColor(MainActivity.this, mColor);複製程式碼
  • 1,設定狀態列顏色
StatusBarUtil.setColor(Activity activity, int color)複製程式碼

  • 2,設定狀態列半透明
StatusBarUtil.setTranslucent(Activity activity, int statusBarAlpha)複製程式碼

  • 3,設定狀態列全透明
StatusBarUtil.setTransparent(Activity activity)複製程式碼

  • 4,為包含 DrawerLayout 的介面設定狀態列顏色(也可以設定半透明和全透明)
StatusBarUtil.setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int color)複製程式碼

  • 5,為使用 ImageView 作為頭部的介面設定狀態列透明(常用的場景為詳情頁的Header部分)
StatusBarUtil.setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView)複製程式碼

  • 6,在 Fragment 中使用

status_uti
status_uti

四、最後

以上就是對於沉浸式狀態列的一些總結,希望可以給還沒有使用沉浸式的同學一些幫助。如果你已經使用過沉浸式狀態列,也不仿看一下,可以對各個版本實現的原理有一個更深的瞭解。最後,推薦了一個不錯的庫,更確切的說,應該是一個不錯的工具類。如有問題,歡迎交流。

更多Android乾貨文章,關注公眾號【Android技術雜貨鋪】

相關文章