標籤:狀態列 android 透明狀態列 沈浸式
前言
這裡不討論[沈浸式]這個詞用得好不好, 大家聽得懂就行. 這篇文章主要是我在實際項目中的一些經驗, 整理出來和大家分享, 歡迎探討. 由於實習一直是996, 沒時間做總結, 今天突然覺得這樣的工作讓我都忘了生活了, 是時候做個了斷了. 寫這篇文章的時候已經是23:44, 來不及貼一些demo, 但是這裡的代碼都是曾經的項目中摘出來的, 是可以啟動並執行, 但我現在沒有真的執行一遍. 注意所有的代碼都只在android 4.4及以上有效.
考慮
根據實際項目的不同, 可能選擇的沈浸式實現策略也會有所不同.
傳統純色actionbar
如果你的app使用的是遵循android規範的actionbar或者有一個純色layout放在螢幕頂部, 那麼這裡有一個入侵較小的方案, 這個方案的實現方式是參考的開源項目SystemBarTint.
讓你的activity繼承一個BaseActivity, 在BaseActivity裡面重寫onPostCreate
@Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ImmerseHelper.setSystemBarTransparent(this); } }
重點在ImmerseHelper這個類裡, 先貼代碼再講解.
public class ImmerseHelper { @TargetApi(Build.VERSION_CODES.KITKAT) public static void setSystemBarTransparent(Activity paramActivity) { Window window = paramActivity.getWindow(); WindowManager.LayoutParams layoutParams = window.getAttributes(); layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; window.setAttributes(layoutParams); hackStatusBarTransparent(paramActivity); setContentPadding(paramActivity); } public static void hackStatusBarTransparent(Activity paramActivity) { ViewGroup localViewGroup = (ViewGroup) paramActivity.getWindow().getDecorView() .findViewById(android.R.id.content); View colorview = new View(paramActivity); colorview.setBackgroundResource(R.color.statusbar_color); localViewGroup.addView(colorview, ViewGroup.LayoutParams.MATCH_PARENT, ImmerseHelper.getStatusBarHeight(paramActivity)); } public static void setContentPadding(Activity activity) { ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0) .setPadding(0, ImmerseHelper.getStatusBarHeight(activity) + ImmerseHelper.getActionBarHeight(activity), 0, 0); } /** * 擷取狀態列高度, 單位px * @param context * @return */ public static int getStatusBarHeight(Context context) { int result = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = context.getResources().getDimensionPixelSize(resourceId); } return result; } /** * 擷取actionbar高度, 單位px * @param context * @return */ public static int getActionBarHeight(Context context) { TypedValue localTypedValue = new TypedValue(); if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, localTypedValue, true)) { return TypedValue.complexToDimensionPixelSize(localTypedValue.data, context.getResources().getDisplayMetrics()); } return 0; }}
簡單來說就是在postCreate時對activity做手腳, 這樣組內其他開發人員幾乎感受不到變化, 只需要將activity的基類指定成BaseActivity就好
ImmerseHelper這個類主要做了三件事
- 將window的FLAG_TRANSLUCENT_STATUS標誌開啟
- 給某個View中添加了一個與狀態列大小完全相同的純色塊
- 給activity的根View設定paddingTop
FLAG_TRANSLUCENT_STATUS這個標誌開啟之後, 狀態列就透明了, 同時我們的activity的主體布局, 也就是setContentView傳入的那個布局, 會頂到螢幕最上方, 被狀態列蓋住一部分, 注意actionbar是不會受影響的. 所以我們在第三步給activity的根view設了paddingTop, 高度是狀態列的高度加上actionbar的高度, 這樣activity中的內容才會回到之前的正常位置. 但此時狀態列下方透明了, 所以我們給某個View中添加了一個與狀態列大小完全相同的色塊, 顏色和actionbar一致, 這樣就有了沈浸式效果.
當然這裡的效果是狀態列下面有一層半透明黑底, 之後才是我們添加的view, 所以不用擔心看不到狀態文字, 在4.4上只能做到這個效果, 5.0上可以讓狀態列底部完全透明, 這個等會兒說.
不過光有實現不行, 還需要知道為什麼我們要這麼做
原理
先看一下hierarchy view的
我們getDecorView拿到的是最左邊的DecorView, 而setContentView影響的是id/content那個view的直接子view, 和id/content平級的view是actionbar.
activity.getWindow().getDecorView().findViewById(android.R.id.content);這句拿到的是id/content這個view, 我們向其中添加一個純色view, 由於開啟了FLAG_TRANSLUCENT_STATUS, 這個純色view就直接頂在最上面, 也就是狀態列覆蓋的地方.
SystemBarTint中並非是向id/content中addView, 而是直接向getDecorView()中addView, 而SwipeBackLayout則是在DecorView和他的子View之間插入自己的layout, 相當於劫持了DecorView的子View, 所以如果同時使用這兩個開源項目不加修改, 要麼滑動返回划走的是狀態列, 要麼狀態列撕裂. 如果像我這麼寫, 就不會和SwipeBackLayout衝突.
非傳統
如果你要做到向這樣, 圖片完全頂在頂部, 那麼就不建議使用actionbar了, 同時也不需要setContentPadding這步. 有時這樣會導致某些頁面的內容過分偏上, 這個時候建議用一個dimen, 在正常情況下是0dp, v19及以上時是24dp, 這也是狀態列高度, 哪些頁面要隔開狀態列, 就用這個dimen做marginTop, 或者include一個高度為這個dimen的layout.
這樣子的入侵比較強, 做新的頁面需要時刻注意和狀態列是否需要保持距離, 很容易忘記, 不過也不算什麼困難, 畢竟一運行就看出來了.
Lolipop+特有方法
上面說的都是api level 19的方法, 唯一的缺陷是狀態列並非完全透明, 而是底部有個半透明的黑條, 在api level 21上, 我們可以去掉這個半透明黑條, 讓狀態列完全透明.
在onCreate的setContentView調用之後, 將activity做參數傳給下面這個方法, 就可以讓你的app在api level 21上擁有完全透明的狀態列, 同時在api 19上使用上面的實現
@TargetApi(Build.VERSION_CODES.LOLLIPOP) public void setSystemBarTransparent(Activity paramActivity) { if (shouldUseTransparentSystemBar()) { Window window = paramActivity.getWindow(); WindowManager.LayoutParams layoutParams = window.getAttributes(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //api 21 解決方案 View systemdecor = window.getDecorView(); systemdecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); layoutParams.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; window.setStatusBarColor(0x00000000); } else { //api 19 解決方案 layoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; } window.setAttributes(layoutParams); } }
api 21的解決方案理論上是可以用xml完成的, 但是我實際測試發現並不能, 只有用代碼才有效.
如果你想問api 20去哪了, 可以去看看sdk manager裡面api 20的括弧裡寫的什麼.
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Android沈浸式狀態列攻略