android狀態列一體化(沉浸式狀態列)

許佳佳233發表於2016-07-27

Android 沉浸式狀態列、狀態列一體化、透明狀態列、仿ios透明狀態列 

http://blog.csdn.net/jdsjlzx/article/details/50437779

注:狀態列的字型顏色位白色, 如果狀態列背景為白色,上面的部落格已經解決了。


原文出處:http://niorgai.github.io/2016/03/20/Android-transulcent-status-bar/ 

最近業務上看到一個設計圖挺好看,所以研究了一下透明狀態列,注意不是沉浸式狀態列,在參考了網上的一些資料後,整理出了這篇部落格.

Github Demo 連結: StatusBarCompat

參考文章:
  1. 由沉浸式狀態列引發的血案

  2. Translucent System Bar 的最佳實踐

  3. 該使用 fitsSystemWindows 了!


首先強調,對於狀態列的處理有兩種不同的方式, 這裡從Translucent System Bar 的最佳實踐直接盜了兩張圖做對比~.

全屏( ContentView 可以進入狀態列)非全屏 ( ContentView 與狀態列分離, 狀態列直接著色)

先定義幾個名詞:

  1. 全屏模式: 左邊圖所示.

  2. 著色模式: 右邊圖所示.

  3. ContentView: activity.findViewById(Window.ID_ANDROID_CONTENT) 獲取的 View , 即 setContentView 方法所設定的 View, 實質為 FrameLayout.

  4. ContentParent: ContentView 的 parent , 實質為 LinearLayout.

  5. ChildView: ContentView 的第一個子 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 來設定狀態列的顏色.

全屏模式:

  1. Window window = activity.getWindow();  
  2. //設定透明狀態列,這樣才能讓 ContentView 向上  
  3. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);   
  4.   
  5. //需要設定這個 flag 才能呼叫 setStatusBarColor 來設定狀態列顏色  
  6. window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   
  7. //設定狀態列顏色  
  8. window.setStatusBarColor(statusColor);  
  9.   
  10. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  11. View mChildView = mContentView.getChildAt(0);  
  12. if (mChildView != null) {  
  13.     //注意不是設定 ContentView 的 FitsSystemWindows, 而是設定 ContentView 的第一個子 View . 使其不為系統 View 預留空間.  
  14.     ViewCompat.setFitsSystemWindows(mChildView, false);  
  15. }  

著色模式:

  1. Window window = activity.getWindow();  
  2. //取消設定透明狀態列,使 ContentView 內容不再覆蓋狀態列  
  3. window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);   
  4.   
  5. //需要設定這個 flag 才能呼叫 setStatusBarColor 來設定狀態列顏色  
  6. window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);   
  7. //設定狀態列顏色  
  8. window.setStatusBarColor(statusColor);  
  9.   
  10. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  11. View mChildView = mContentView.getChildAt(0);  
  12. if (mChildView != null) {  
  13.     //注意不是設定 ContentView 的 FitsSystemWindows, 而是設定 ContentView 的第一個子 View . 預留出系統 View 的空間.  
  14.     ViewCompat.setFitsSystemWindows(mChildView, true);  
  15. }  

4.4-5.0的處理:

4.4-5.0因為沒有直接的 API 可以呼叫,需要自己相容處理,網上的解決方法基本都是建立一下高度為狀態列的 View ,通過設定這個 View 的背景色來模擬狀態列. 這裡我嘗試了三種方法來相容處理.

方法1: 向 ContentView 新增假 View , 設定 ChildView 的 marginTop 屬性來模擬 fitsSystemWindows .

全屏模式:

  1. Window window = activity.getWindow();  
  2. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  3.   
  4. //首先使 ChildView 不預留空間  
  5. View mChildView = mContentView.getChildAt(0);  
  6. if (mChildView != null) {  
  7.     ViewCompat.setFitsSystemWindows(mChildView, false);  
  8. }  
  9.   
  10. int statusBarHeight = getStatusBarHeight(activity);  
  11. //需要設定這個 flag 才能設定狀態列  
  12. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  13. //避免多次呼叫該方法時,多次移除了 View  
  14. if (mChildView != null && mChildView.getLayoutParams() != null && mChildView.getLayoutParams().height == statusBarHeight) {  
  15.     //移除假的 View.  
  16.     mContentView.removeView(mChildView);  
  17.     mChildView = mContentView.getChildAt(0);  
  18. }  
  19. if (mChildView != null) {  
  20.     FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();  
  21.     //清除 ChildView 的 marginTop 屬性  
  22.     if (lp != null && lp.topMargin >= statusBarHeight) {  
  23.         lp.topMargin -= statusBarHeight;  
  24.         mChildView.setLayoutParams(lp);  
  25.     }  
  26. }  

著色模式:

  1. Window window = activity.getWindow();  
  2. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  3.   
  4. //First translucent status bar.  
  5. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  6. int statusBarHeight = getStatusBarHeight(activity);  
  7.   
  8. View mChildView = mContentView.getChildAt(0);  
  9. if (mChildView != null) {  
  10.     FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mChildView.getLayoutParams();  
  11.     //如果已經為 ChildView 設定過了 marginTop, 再次呼叫時直接跳過  
  12.     if (lp != null && lp.topMargin < statusBarHeight && lp.height != statusBarHeight) {  
  13.         //不預留系統空間  
  14.         ViewCompat.setFitsSystemWindows(mChildView, false);   
  15.         lp.topMargin += statusBarHeight;  
  16.         mChildView.setLayoutParams(lp);  
  17.     }  
  18. }  
  19.   
  20. View statusBarView = mContentView.getChildAt(0);  
  21. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == statusBarHeight) {  
  22.     //避免重複呼叫時多次新增 View  
  23.     statusBarView.setBackgroundColor(statusColor);  
  24.     return;  
  25. }  
  26. statusBarView = new View(activity);  
  27. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  
  28. statusBarView.setBackgroundColor(statusColor);  
  29. //向 ContentView 中新增假 View  
  30. mContentView.addView(statusBarView, 0, lp);  

方法2: 向 ContentParent 新增假 View ,設定 ContentView 和 ChildView 的 fitsSystemWindows.

全屏模式:

  1. Window window = activity.getWindow();  
  2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  3.   
  4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  5. ViewGroup mContentParent = (ViewGroup) mContentView.getParent();  
  6.   
  7. View statusBarView = mContentParent.getChildAt(0);  
  8. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
  9.     //移除假的 View  
  10.     mContentParent.removeView(statusBarView);  
  11. }  
  12. //ContentView 不預留空間  
  13. if (mContentParent.getChildAt(0) != null) {  
  14.     ViewCompat.setFitsSystemWindows(mContentParent.getChildAt(0), false);  
  15. }  
  16.   
  17. //ChildView 不預留空間  
  18. View mChildView = mContentView.getChildAt(0);  
  19. if (mChildView != null) {  
  20.     ViewCompat.setFitsSystemWindows(mChildView, false);  
  21. }  

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

  1. Window window = activity.getWindow();  
  2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  3.   
  4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  5. ViewGroup mContentParent = (ViewGroup) mContentView.getParent();  
  6.   
  7. View statusBarView = mContentParent.getChildAt(0);  
  8. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
  9.     //避免重複呼叫時多次新增 View  
  10.     statusBarView.setBackgroundColor(statusColor);  
  11.     return;  
  12. }  
  13.   
  14. //建立一個假的 View, 並新增到 ContentParent  
  15. statusBarView = new View(activity);  
  16. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  17.         getStatusBarHeight(activity));  
  18. statusBarView.setBackgroundColor(statusColor);  
  19. mContentParent.addView(statusBarView, 0, lp);  
  20.   
  21. //ChildView 不需要預留系統空間  
  22. View mChildView = mContentView.getChildAt(0);  
  23. if (mChildView != null) {  
  24.     ViewCompat.setFitsSystemWindows(mChildView, false);  
  25. }  

方法3:向 ContentView 新增假 View , 設定 ChildView 的 fitsSystemWindows.

全屏模式:

  1. Window window = activity.getWindow();  
  2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  3.   
  4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  5. View statusBarView = mContentView.getChildAt(0);  
  6. //移除假的 View  
  7. if (statusBarView != null && statusBarView.getLayoutParams() != null && statusBarView.getLayoutParams().height == getStatusBarHeight(activity)) {  
  8.     mContentView.removeView(statusBarView);  
  9. }  
  10. //不預留空間  
  11. if (mContentView.getChildAt(0) != null) {  
  12.     ViewCompat.setFitsSystemWindows(mContentView.getChildAt(0), false);  
  13. }  

著色模式:

  1. Window window = activity.getWindow();  
  2. window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);  
  3.   
  4. ViewGroup mContentView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);  
  5. int statusBarHeight = getStatusBarHeight(activity);  
  6.   
  7. View mTopView = mContentView.getChildAt(0);  
  8. if (mTopView != null && mTopView.getLayoutParams() != null && mTopView.getLayoutParams().height == statusBarHeight) {  
  9.     //避免重複新增 View  
  10.     mTopView.setBackgroundColor(statusColor);  
  11.     return;  
  12. }  
  13. //使 ChildView 預留空間  
  14. if (mTopView != null) {  
  15.     ViewCompat.setFitsSystemWindows(mTopView, true);  
  16. }  
  17.   
  18. //新增假 View  
  19. mTopView = new View(activity);  
  20. ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight);  
  21. mTopView.setBackgroundColor(statusColor);  
  22. mContentView.addView(mTopView, 0, lp);  

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

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

 方法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 也不會有影響.


推薦乾貨集中營Android客戶端,實現了沉浸式狀態列,無縫換膚,帶3D感覺的側滑選單

主頁及側滑頁截圖


gank1.png

福利及換膚截圖


gank2.png

下載地址:乾貨集中營
github:GanK

相關文章