1. 程式人生 > >Android沉浸式狀態列與EditText問題【沉浸式狀態列實現及遇到的坑】

Android沉浸式狀態列與EditText問題【沉浸式狀態列實現及遇到的坑】

Android4.4以前的版本,狀態列都是一塊黑色的,個人認為還是比較醜的。自4.4開始,Android已經支援透明狀態列了(俗稱沉浸式狀態列)。個人認為支援沉浸式狀態列的app逼格還是比較高的,為了緊跟潮流,我們專案中也準備加入沉浸式狀態列。在實現沉浸式狀態列的過程中踩了不少的坑,特此記錄下來。

如何實現狀態列 Android 4.4以上實現方式 Android 4.4版本提供了FLAG_TRANSLUCENT_STATUS,在Activity中加入此flag,可以設定狀態列透明。程式碼如下: 1 2 3 4 5 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {     Window window = getWindow();     // Translucent status bar     window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } 僅僅設定FLAG_TRANSLUCENT_STATUS,你會發現介面上的ToolBar會跑到狀態列上面去,如下圖:

通常我們會使用fitsSystemWindows屬性來解決此問題。

fitSystemWindows官方描述: Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity. 簡單描述: 這個屬性的作用是讓view可以根據系統視窗(如status bar)來調整自己的佈局,如果值為true,就會調整view的paingding屬性來給system windows留出空間(即給view新增一個值為狀態列高度的top padding)。 我們試著給ToolBar設定一下fitsSystemWindows屬性為true。佈局程式碼如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:id="@+id/activity_main"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical"     tools:context="example.com.chenjinshidemo.MainActivity">

    <android.support.v7.widget.Toolbar         android:id="@+id/my_toolbar"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:background="@color/colorPrimary"         android:minHeight="?attr/actionBarSize"         android:fitsSystemWindows="true"         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

    <FrameLayout         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="1"         android:padding="16dp">

        <RelativeLayout             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:background="#e0e0e0"             android:layout_gravity="bottom">             <EditText                 android:layout_width="match_parent"                 android:layout_height="40dp"                 android:fitsSystemWindows="true"                 android:background="@drawable/edit_text_rect_bg" />         </RelativeLayout>     </FrameLayout> </LinearLayout> 4.4的效果圖如下:

注:有些4.4的系統上面狀態列並不是全透明的,而是漸變的。

Android 5.0以上實現方式 你會發現,已經實現了沉浸式狀態列效果了。如果執行在5.0以上的機器上面,會發現大部分手機會出現狀態列是半透明的,效果圖如下:

我們能不能讓將5.0以上的手機也設定為和4.4一樣的全透明的狀態列呢?答案是肯定的!Android自5.0起,又為我們提供了設定狀態列顏色的API,我們可以自己設定狀態列的顏色。 在程式碼中再加入如下程式碼: 1 2 3 4 5 6 7 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {     window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);     window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);     window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);     window.setStatusBarColor(Color.TRANSPARENT); } 再在執行看看效果,狀態列已經變成全透明瞭。6.0執行效果圖和上面4.4一樣,就不再附圖了。

Android 6.0以上設定狀態列字型顏色 預設狀態列字型顏色是白色的,如果ToolBar的顏色較淺,那麼狀態列上白色的字看不怎麼清楚。

Android6.0以後,我們可以使用程式碼將狀態列字型的顏色設定為黑色了,程式碼如下: 1 win.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 設定了深色狀態列字型的效果圖如下:

踩過的坑 如果你認為已經已經完美實現了,那真是too young to simple。下面是一些我踩過的坑。

與軟鍵盤衝突的坑 如果在介面中有EditText的話,你會發現當軟體盤彈出的時候(Activity已經設定了adjustResize),ToolBar的內容都被頂上去了,但是EditText輸入框卻被有頂上來(正常情況應該是ToolBar沒事,輸入框被軟鍵盤頂上去),如下圖:

這是為什麼呢?經研究發現原來是fitsSystemWindows屬性搞的鬼。哪個View設定了fitsSystemWindows=true,這個View就會被軟體盤頂上去。所以說,fitsSystemWindows不能亂用,會有意想不到的坑。

那能不能不用fitsSystemWindows呢?既然上面說了,fitsSystemWindows=true的作用是給View新增值為狀態列高度的padding,那我們何不自己手動給ToolBar新增padding呢? 我們去掉ToolBar上的fitsSystemWindows屬性,並設定一下ToolBar的padding,程式碼如下: 1 2 3 4 5 6 7 8 9 10 11 12

protected void setStatusBarPaddingAndHeight(View toolBar) {     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {         if (toolBar != null) {             int statusBarHeight = getSystemBarHeight(this);             toolBar.setPadding(toolBar.getPaddingLeft(), statusBarHeight, toolBar.getPaddingRight(),                     toolBar.getPaddingBottom());             toolBar.getLayoutParams().height = statusBarHeight +                     (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45, getResources().getDisplayMetrics());         }     } } 去掉ToolBar的fitsSystemWindows屬性,並加上加上上面的程式碼,軟鍵盤彈出時ToolBar正常了,但是輸入框還是沒有彈出來。

解決方式1 剛才上面給ToolBar設定了fitsSystemWindows=true,結果ToolBar的內容被頂上去了,那我們能不能給輸入框設定一個fitsSystemWindows=true屬性呢?試一下就知道了! 試了之後你會發現,果然可以,但是輸入框的高度變了,其實是輸入框的padding增加了狀態列的高度。如果設計和產品能接受這種效果,那這也不失為一種解決方法。很顯然,一般都不會接受這種效果的,就算設計和產品能接受,我們開發也不能接受!

那有沒有更好的方法呢?到網上搜索發現下面一種解決方案。

解決方式2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class AndroidBug5497Workaround {

    public static void assistActivity(View content) {         new AndroidBug5497Workaround(content);     }

    private View mChildOfContent;     private int usableHeightPrevious;     private ViewGroup.LayoutParams frameLayoutParams;

    private AndroidBug5497Workaround(View content) {         if (content != null) {             mChildOfContent = content;             mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {                 public void onGlobalLayout() {                     possiblyResizeChildOfContent();                 }             });             frameLayoutParams = mChildOfContent.getLayoutParams();         }     }

    private void possiblyResizeChildOfContent() {         int usableHeightNow = computeUsableHeight();         if (usableHeightNow != usableHeightPrevious) {             //如果兩次高度不一致             //將計算的可視高度設定成檢視的高度             frameLayoutParams.height = usableHeightNow;             mChildOfContent.requestLayout();//請求重新佈局             usableHeightPrevious = usableHeightNow;         }     }

    private int computeUsableHeight() {         //計算檢視可視高度         Rect r = new Rect();         mChildOfContent.getWindowVisibleDisplayFrame(r);         return (r.bottom);     }

} 新增上面的類,然後在Activity的onCreate方法中的setContentView後面加上如下程式碼: 1 AndroidBug5497Workaround.assistActivity(findViewById(android.R.id.content)); 然後執行,輸入框能夠正常被頂上去,而且輸入框的佈局有沒有受到影響。

該解決方案的原理是,給介面的根佈局設定一個監聽器,當介面大小有變化的時候,如鍵盤彈出的時候,重新設定一下根佈局的高度,再呼叫requestLayout對介面進行重繪。

注:不知道這種解決方案會不會引起其他的問題,目前暫時沒有發現,如果哪位知道有什麼問題,請指點一下,謝謝!

華為EMUI3.1上的坑 將上面的沉浸式程式碼放在EMUI3.1系統的手機(如華為榮耀7)上面跑,你會發現,根本沒有沉浸式效果,狀態列是透明的,顯示的是桌面上的顏色,如下圖:

經驗證,原來是EMUI3.1系統的原因,很多App(如網易雲音樂等)也是在EMUI3.0上有沉浸式的效果,到了EMUI3.1卻沒有效果了。在EMUI3.1沒有沉浸式效果如果和4.4以前一樣是黑的也就算了,這樣透明的顯示桌面顏色實在難看。 後來發現去掉下面這句程式碼,可以讓其有沉浸式的效果。 1 window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 效果如下:

不過它的狀態列不是全透明的,而是像某些4.4的系統一樣是漸變的,不過總比原來的效果好。 這裡我們加一個判斷,判斷如果不是EMUI3.1的系統,才呼叫clearFlags清除掉FLAG_TRANSLUCENT_STATUS。 具體程式碼如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {    Window window = getWindow();    window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {       // 因為EMUI3.1系統與這種沉浸式方案API有點衝突,會沒有沉浸式效果。       // 所以這裡加了判斷,EMUI3.1系統不清除FLAG_TRANSLUCENT_STATUS       if (!isEMUI3_1()) {          window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);       }       window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN             | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);       window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);       window.setStatusBarColor(Color.TRANSPARENT);    } } public static boolean isEMUI3_1() {     if ("EmotionUI_3.1".equals(getEmuiVersion())) {         return true;     }     return false; }

private static String getEmuiVersion(){     Class<?> classType = null;     try {         classType = Class.forName("android.os.SystemProperties");         Method getMethod = classType.getDeclaredMethod("get", String.class);         return (String)getMethod.invoke(classType, "ro.build.version.emui");     } catch (ClassNotFoundException e) {         DebugUtil.exception(TAG,e);     } catch (NoSuchMethodException e) {         DebugUtil.exception(TAG,e);     } catch (IllegalAccessException e) {         DebugUtil.exception(TAG,e);     } catch (InvocationTargetException e) {         DebugUtil.exception(TAG,e);     } catch (Exception e){         DebugUtil.exception(TAG,e);     }     return ""; } ActionMode上的坑 ActionMode是一種Context Menu,它懸浮在ToolBar活著ActionBar上面。現在已經基本上很少app在用ActionMode了,所以可能很多人可能沒有用過,沒用過的可以看看這篇文章http://blog.csdn.net/xyz_lmn/article/details/12754785。

公司專案中使用到了ActionMode(歷史遺留程式碼),在實現沉浸式的效果中,發現ActionMode並不支援沉浸式。ActionMode彈出來的時候,狀態列會變成黑色的,效果如下: ActionMode彈出前:

ActionMode彈出後:

遇到這個問題的時候,第一想法就是能不能和ToolBar一樣給ActionMode設定一個值為狀態列高度的padding,然後將它頂到狀態列裡面去。 在Stackoverflow上面搜了一種方法可以將ActionMode頂到狀態列上面去,給Activity加一個Flag即可,程式碼如下: 1 getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 看到能將ActionMode頂到狀態列中去時心裡已經在開始偷著樂了,接下來只要給ActionMode設定一個padding即可。然而發現ActionMode根本沒有提供在程式碼中設定高度和padding的API,只能在style中設定高度和padding。這樣就有一個問題,因為Android手機碎片化嚴重,導致不同廠商的不同手機狀態列的高度不一致,所以使用這個方法會出現有的手機ActionMode彈出時比ToolBar高或者低,不過也還能接受。

如果僅僅這樣也就算了,沒想到又引起了另外一個問題。在使用上面的flag之後(flag不能亂加啊,血和淚的教訓),雖然ActionMode頂到狀態列了,但是在某些(如華為)帶虛擬按鍵的手機(虛擬按鍵對開發者來說也是一個大坑),虛擬按鍵會遮擋底部的佈局。 只能放棄這種方案,尼瑪,怎麼這麼多坑,讓我哭會(淚崩)!

沒辦法,問題還是得去解決啊!繼續尋找其它解決方案。。。

這時候想到了在Android5.0以上我們可以設定狀態列的顏色,那可不可以在ActionMode彈出來的時候,給狀態列設定一個與ToolBar顏色一致的顏色呢?嘗試一下吧,在BaseActivity中重寫startSupportActionMode方法,在裡面給狀態列設定顏色,具體程式碼如下: 1 2 3 4 5 6 7 @Override public ActionMode startSupportActionMode(ActionMode.Callback callback) {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {       getWindow().setStatusBarColor(ContextCompat.getColor(getApplicationContext(), R.color.actionbar_background));    }    return super.startSupportActionMode(callback); } 沒想到,居然可以。不過只能相容5.0以上的手機,4.4還是黑色。目前也只能這樣了,後期專案中估計會將ActionMode幹掉吧,到時候就OK了。如果大家又更好相容ActionMode的方法請指點一下,謝謝!!! --------------------- 作者:FCFRT 來源:CSDN 原文:https://blog.csdn.net/qq_27809865/article/details/74081709 版權宣告:本文為博主原創文章,轉載請附上博文連結!