Android-transulcent-status-bar

最近業務上看到一個設計圖挺好看,因此研究了一下透明狀態欄,注意不是沉浸式狀態欄,在參考了網上的一些資料後,整理出了這篇博客.java

Github Demo 連接: StatusBarCompatandroid

參考文章:

  1. 由沉浸式狀態欄引起的血案
  2. Translucent System Bar 的最佳實踐
  3. 該使用 fitsSystemWindows 了!

首先強調,對於狀態欄的處理有兩種不一樣的方式, 這裏從Translucent System Bar 的最佳實踐直接盜了兩張圖作對比~.git

全屏( ContentView 能夠進入狀態欄) 非全屏 ( ContentView 與狀態欄分離, 狀態欄直接着色)

先定義幾個名詞:github

  1. 全屏模式: 左邊圖所示.
  2. 着色模式: 右邊圖所示.
  3. ContentViewactivity.findViewById(Window.ID_ANDROID_CONTENT) 獲取的 View , 即 setContentView 方法所設置的 View, 實質爲 FrameLayout.
  4. ContentParentContentView 的 parent , 實質爲 LinearLayout.
  5. ChildViewContentView 的第一個子 View ,即佈局文件中的 layout .

再介紹一下相關的函數:函數

  1. fitsSystemWindows, 該屬性能夠設置是否爲系統 View 預留出空間, 當設置爲 true 時,會預留出狀態欄的空間.
  2. ContentView, 實質爲 ContentFrameLayout, 可是重寫了 dispatchFitSystemWindows 方法, 因此對其設置 fitsSystemWindows 無效.
  3. ContentParent, 實質爲 FitWindowsLinearLayout, 裏面第一個 View 是 ViewStubCompat, 若是主題沒有設置 title ,它就不會 inflate .第二個 View 就是 ContentView.

5.0以上的處理:

自5.0引入 Material Design ,狀態欄對開發者更加直接,能夠直接調用 setStatusBarColor 來設置狀態欄的顏色.佈局

全屏模式:ui

Window window = activity.getWindow(); //設置透明狀態欄,這樣才能讓 ContentView 向上 window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //須要設置這個 flag 才能調用 setStatusBarColor 來設置狀態欄顏色 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //設置狀態欄顏色 window.setStatusBarColor(statusColor); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); View mChildView = mContentView.getChildAt(0); if (mChildView != null) { //注意不是設置 ContentView 的 FitsSystemWindows, 而是設置 ContentView 的第一個子 View . 使其不爲系統 View 預留空間. ViewCompat.setFitsSystemWindows(mChildView, false); } 

着色模式:spa

Window window = activity.getWindow(); //取消設置透明狀態欄,使 ContentView 內容再也不覆蓋狀態欄 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //須要設置這個 flag 才能調用 setStatusBarColor 來設置狀態欄顏色 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //設置狀態欄顏色 window.setStatusBarColor(statusColor); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); View mChildView = mContentView.getChildAt(0); if (mChildView != null) { //注意不是設置 ContentView 的 FitsSystemWindows, 而是設置 ContentView 的第一個子 View . 預留出系統 View 的空間. ViewCompat.setFitsSystemWindows(mChildView, true); } 

4.4-5.0的處理:

4.4-5.0由於沒有直接的 API 能夠調用,須要本身兼容處理,網上的解決方法基本都是建立一下高度爲狀態欄的 View ,經過設置這個 View 的背景色來模擬狀態欄. 這裏我嘗試了三種方法來兼容處理.設計

方法1: 向 ContentView 添加假 View , 設置 ChildView 的 marginTop 屬性來模擬 fitsSystemWindows .code

全屏模式:

Window window = activity.getWindow(); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); //首先使 ChildView 不預留空間 View mChildView = mContentView.getChildAt(0); if (mChildView != null) { ViewCompat.setFitsSystemWindows(mChildView, false); } int statusBarHeight = getStatusBarHeight(activity); //須要設置這個 flag 才能設置狀態欄 window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //避免屢次調用該方法時,屢次移除了 View if (mChildView != null && mChildView.getLayoutParams() != null && mChildView.getLayoutParams().height == statusBarHeight) { //移除假的 View. mContentView.removeView(mChildView); mChildView = mContentView.getChildAt(0); } if (mChildView != null) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams(); //清除 ChildView 的 marginTop 屬性 if (lp != null && lp.topMargin >= statusBarHeight) { lp.topMargin -= statusBarHeight; mChildView.setLayoutParams(lp); } } 

着色模式:

Window window = activity.getWindow(); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); //First translucent status bar. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); int statusBarHeight = getStatusBarHeight(activity); View mChildView = mContentView.getChildAt(0); if (mChildView != null) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams(); //若是已經爲 ChildView 設置過了 marginTop, 再次調用時直接跳過 if (lp != null && lp.topMargin < statusBarHeight && lp.height != statusBarHeight) { //不預留系統空間 ViewCompat.setFitsSystemWindows(mChildView, false); lp.topMargin += statusBarHeight; mChildView.setLayoutParams(lp); } } View statusBarView = mContentView.getChildAt(0); if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == statusBarHeight) { //避免重複調用時屢次添加 View statusBarView.setBackgroundColor(statusColor); return; } statusBarView = new View(activity); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight); statusBarView.setBackgroundColor(statusColor); //向 ContentView 中添加假 View mContentView.addView(statusBarView, 0, lp); 

方法2: 向 ContentParent 添加假 View ,設置 ContentView 和 ChildView 的 fitsSystemWindows.

全屏模式:

Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); ViewGroup mContentParent = (ViewGroup) mContentView.getParent(); View statusBarView = mContentParent.getChildAt(0); if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) { //移除假的 View mContentParent.removeView(statusBarView); } //ContentView 不預留空間 if (mContentParent.getChildAt(0) != null) { ViewCompat.setFitsSystemWindows(mContentParent.getChildAt(0), false); } //ChildView 不預留空間 View mChildView = mContentView.getChildAt(0); if (mChildView != null) { ViewCompat.setFitsSystemWindows(mChildView, false); } 

着色模式(會有一條黑線,沒法解決):

Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); ViewGroup mContentParent = (ViewGroup) mContentView.getParent(); View statusBarView = mContentParent.getChildAt(0); if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) { //避免重複調用時屢次添加 View statusBarView.setBackgroundColor(statusColor); return; } //建立一個假的 View, 並添加到 ContentParent statusBarView = new View(activity); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); statusBarView.setBackgroundColor(statusColor); mContentParent.addView(statusBarView, 0, lp); //ChildView 不須要預留系統空間 View mChildView = mContentView.getChildAt(0); if (mChildView != null) { ViewCompat.setFitsSystemWindows(mChildView, false); } 

方法3:向 ContentView 添加假 View , 設置 ChildView 的 fitsSystemWindows.

全屏模式:

Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); View statusBarView = mContentView.getChildAt(0); //移除假的 View if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) { mContentView.removeView(statusBarView); } //不預留空間 if (mContentView.getChildAt(0) != null) { ViewCompat.setFitsSystemWindows(mContentView.getChildAt(0), false); } 

着色模式:

Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); int statusBarHeight = getStatusBarHeight(activity); View mTopView = mContentView.getChildAt(0); if (mTopView != null && mTopView.getLayoutParams() != null && mTopView.getLayoutParams().height == statusBarHeight) { //避免重複添加 View mTopView.setBackgroundColor(statusColor); return; } //使 ChildView 預留空間 if (mTopView != null) { ViewCompat.setFitsSystemWindows(mTopView, true); } //添加假 View mTopView = new View(activity); ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight); mTopView.setBackgroundColor(statusColor); mContentView.addView(mTopView, 0, lp); 

其實全屏模式在三種模式下實現都是同樣的,主要是着色模式實現不一樣.

 

public static int getStatusHeight(Activity activity) {
        int statusHeight = 0;
        Rect localRect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(localRect);
        statusHeight = localRect.top;
        if (0 == statusHeight) {
            Class<?> localClass;
            try {
                localClass = Class.forName("com.android.internal.R$dimen");
                Object localObject = localClass.newInstance();
                int i5 = Integer.parseInt(localClass.getField("status_bar_height").get(localObject).toString());
                statusHeight = activity.getResources().getDimensionPixelSize(i5);
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | IllegalArgumentException | SecurityException | NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        return statusHeight;
    }

  

對比一下三種着色模式實現的方式:

  方法1 方法2 方法3
原理 向 ContentView 中添加假 View, 而後利用 ChildView的 marginTop 屬性來模擬 fitsSystemWindows ,主要是經過修改 marginTop 的值能夠在全屏模式和着色模式之間切換. 由於 ParentView 的實質是一個 LinearLayout , 能夠再其頂部添加 View . 向 ContentView 中添加假 View, 而後利用 ChildView 的 fitsSystemWindows 屬性來控制位置, 可是實現缺陷就是不能隨時切換兩種模式.
缺陷 改變了 ChildView 的 marginTop 值 着色模式下,會像由沉浸式狀態欄引起的血案中同樣出現一條黑線 不能在不重啓 Activity 的狀況下切換模式.
對應 Github demo 中代碼 StatusBarCompat類 StatusBarCompat1類 StatusBarCompat2 類

總結

  • StatusBarCompat2 主要問題不能切換.
  • StatusBarCompat1 在4.4上會有一條黑線, 若是能夠解決我以爲這是最靠譜的解決方法.
  • StatusBarCompat 類算是我最後給出的解決方案吧, 目前使用效果比較完善.推薦使用
    • 用戶能夠隨時在同一個 Activity 中切換不一樣的狀態欄模式.
    • 就算子 View 重寫了 dispatchFitSystemWindows 也不會有影響.
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息