GitHub源碼:TransparentStatusbar
源碼中分兩個app
認識透明狀態欄
從Android4.4
開始引入了透明狀態欄的新特性.
見下圖,左邊爲傳統的Android
系統狀態欄,右邊爲透明狀態欄.
- 正常顯示狀態欄的圖標/文字
- 狀態欄的背景是透明的,能透出應用的背景色.而不像之前一樣是默認的黑色不可編輯.
透明狀態欄Api及特性
從Android 4.4(v19)
開始,透明狀態欄特性變化很頻繁,直到Android 6.0(v23)
才真正完善穩定.
下表展示各版本所引入的新Api
或特性.
Version/level | Features | Description |
---|
4.4/v19 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | 狀態欄是漸變色的半透明 | |
4.4_Watch/v20 | OnApplyWindowInsetsListener | 能夠區分多個Inset事件與Rect信息(PS.系統狀態欄屬於插入區Inset的一種) |
5.0/v21 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 允許自定義狀態欄背景色了,但無法控制狀態欄上的文字/圖標顏色 |
6.0/v23 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | 狀態欄上的圖標/文字顏色的亮色模式,即顏色是暗色 |
設置透明狀態欄
根據多個版本間的Api
及特性,Java
代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { } window.getDecorView().setSystemUiVisibility(visibility); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }
|
注意: 要設置透明狀態欄的Activity
其theme
須是NoTitleBar
.
1 2 3 4 5 6 7 8 9 10
| <activity android:name="MyActivity" android:theme="@android:style/Theme.NoTitleBar" /> public void onCreate(Bundle bundle) { super.onCreate(bundle); requestWindowFeature(Window.FEATURE_NO_TITLE); }
|
代碼執行後,界面顯示效果如下圖:
可以發現系統狀態欄的區域已經消失,Activity
的contentView頂上去佔據了原來屬於系統狀態欄的區域.
導致雖然Back
Title
雖然仍在Titlebar
區域垂直居中,但視覺效果上受狀態欄圖標的影響,卻不是垂直居中的效果.
所以接下來第二步就是: 以何種方式處理消失的系統狀態欄區域?
處理消失的系統狀態欄區域
處理方式可以有:
- 就讓
activity
的contentView
頂上去吧,不需要修改. - 調整
Titlebar
的高度.- 可以通過設置
paddingTop
/layout_height
. - 對於不同的Android版本,可以通過版本適配文件,在
values/dimens.xml
和values-v19/dimens.xml
分別定義具體數值. - 調整
paddingTop
,有可能導致Titlebar
中的內容不會再垂直居中. - 不適應無
Titlebar
的activity
.
android:fitsSystemWindows
&& OnApplyWindowInsetsListener
fitsSystemWindows
標籤可以直接對View
添加paddingXXX
- 從
activity
的佈局嵌套結構中只對第一個設置fitsSystemWidnows
的View
有效,無法設置設置到多個View
- 方式單一,對於指定的界面實現簡單,但要應用於整個
App
超多Activity
的layout.xml
,不夠靈活. - 不夠靈活還表現在:有些
activity
的複雜效果可能會有多個View
同時或分場合佔據系統狀態欄的空間,需要留出額外的修改接口.
- 在
activity
的contentView
的頂部再addView
直接填充原來狀態欄的區域. OnApplyWindowInsetsListener
可以回調給開發者當前WindowInset的區域類型與區域寬高Rect信息- 和
fitsSystemWindows
一樣,多個View
設置該監聽但也只有最外層的view會被調用執行.
在實踐中,本人採用了1
2
5
這三種方式配合使用.
fitsSystemWindows
我並不想用該屬性.
這裏只記錄一下系統源碼中的相應的方法:
1 2 3 4 5 6
| View.java public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) private boolean fitSystemWindowsInt(Rect insets) // 這個方法是真正爲View添加paddingXXX的地方 protected void internalSetPadding(int left, int top, int right, int bottom)
|
調試系統源碼的一個方法:
使用Android
自帶模擬器Debug
,斷點跟進執行過程.要注意,模擬器的apk level
要和compileSdkVersion
及buildToolsVersion
相對應.
Activity中的接口設計
接口設計的原則:
- 對正常的業務佈局xml的編寫沒有強制要求
如不要求強制使用fitsSystemWindows
- 不影響正常的業務Activity的java代碼編寫
如業務Actiivty不需要額外的編碼量即可實現透明狀態欄效果.特殊的動效Activity除外. - 提供靈活的處理方式
可方便的開啓或關閉透明狀態欄功能.
類圖如下:
BaseActivity
是App
中所有Activity
的父類.
由於透明狀態欄與Activity
相關,所以對應的接口聲明都放在BaseActivity
中.
默認Activity
的透明狀態欄功能是開啓的.
該類中幾個重要函數的調用順序爲:
1 2
| `onCreate` → `setContentView` → `isFixTransparentStatusbar` └──true→ `fixTransparentStatusbar`
|
具體代碼實現爲:
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 42 43
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void setContentView(View view) { rootView = view; super.setContentView(view); if (isFixTransparentStatusBar()) { Window window = getWindow(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { int visibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; } window.getDecorView().setSystemUiVisibility(visibility); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); fixTransparentStatusBar(view); fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); fixTransparentStatusBar(view); fixTransparentStatusBarWhiteTextColor(view, viewStatusbarBackground); } else { setStatusbarBackgroundGone(); } } else { setStatusbarBackgroundGone(); } }
|
注意 : 由於透明狀態欄是執行Window.addFlags()
實現的,該方法又調用了Window.setFlags()
.
閱讀該Api
文檔,發現推薦先執行setContentView
後執行Window.setFlags()
.
TitlebarActivity
是通用的包含Titlebar
的Activity
.
重載了setContentView()
,實現自動添加Titlebar
這個通用組件,當然不需要Titlebar
時也可以使用setContentViewNoTitlebar()
.
擴展此功能,即在添加通用Titlebar
前先添加上通用的viewStatusbarBackground
.
setContentView()
的實現爲:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Override public void setContentView(View view) { contentView = view; LinearLayout linearLayout = new LinearLayout(this); linearLayout.setOrientation(LinearLayout.VERTICAL); LayoutInflater.from(this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout, true); viewStatusbarBackground = linearLayout.findViewById(R.id.status_bar_background); LayoutInflater.from(this).inflate(R.layout.titlebar_original, linearLayout, true); viewTitlebar = linearLayout.findViewById(R.id.titlebar_layout); initTitlebarIDs(viewTitlebar); linearLayout.addView(contentView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); super.setContentView(linearLayout); }
|
其中,爲了便於定位到Titlebar
及viewStatusbarBackground
,這兩個組件的id
都被預先定義在attrs.xml
中.
1 2 3 4 5
| <resources> <item name="status_bar_background" type="id"/> <item name="titlebar_layout" type="id"/> </resources>
|
Fragment中的接口設計
有的Activity
的顯示主體是Fragment
,接口設計的觀點爲不應干擾Fragment
正常的onCreateView()
的實現流程.
那麼在哪個時機處理Fragment
的contentView
呢?
閱讀Api
發現了Fragment::onViewCreated(View view)
這個方法,該方法會在onCreateView()
返回後,立即執行,且方法參數爲onCreateView()
所返回的View
.
Java代碼實現如下:
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
| public class BaseFragment extends Fragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isFixTransparentStatusBar()) { fixTransparentStatusBar(view); } super.onViewCreated(view, savedInstanceState); } /** * 是否需要改變status bar背景色,對於某些機型手機(如oppo)無法改變狀態欄字體顏色, * 會被當前狀態欄擋住字體顏色,因此修改透明狀態欄背景色 * @return true: 調用fixTransparentStatusBar() */ protected boolean isFixTransparentStatusBar(){ return false; } /** * @param view {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}中返回的view. * */ protected void fixTransparentStatusBar(View view) { } }
|
通過fixTransparentStatusBar()
,即可以調整Fragment
的界面顯示,無論是往狀態欄區域添加一個填充View
或根據id
再調整寬高或padding
都是可以的.
白色Titlebar
的處理
Android 6.0
及以上可以使用亮色模式.
但在是低版本的手機中,Titlebar
如果是白色的,或者說App
的主題是白色的,則會出現狀態欄的白色文字和圖標被淹沒在Titlebar
中無法閱讀.如下圖:
這時可以通過layer-list
來設置分層背景,不必新增額外的View
填充系統狀態欄區域.
見如下代碼或TestBasic/res/drawable/title_layout_white3.xml
:
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
| <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> <solid android:color="@android:color/black" /> </shape> </item> <item android:bottom="1dp"> <shape android:shape="rectangle"> <solid android:color="@android:color/white" /> </shape> </item> <item android:bottom="48dp"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="@android:color/white" android:centerColor="@color/middleColor" android:endColor="@android:color/darker_gray" android:angle="90" /> </shape> </item> </layer-list>
|
- 第一個
item
爲黑色背景,效果爲Titlebar
底下的黑色分隔線. - 第二個
item
爲常規的Titlebar
背景. - 第三個
item
爲狀態欄的過濾漸變背景色.
最張效果見下圖:
React-Native的處理
React-Native
是js
代碼,怎麼辦?
不不,React
是表象,Native
是實質。一樣處理掉。
React-Native
最root
的組件界面是ReactRootView
,可以在顯示的Activity
里布局使用LinearLayout
,orientation
爲VERTICAL
,
將TransparentStatusBar
及ReactRootView
一併添加爲子View
,設置該LinearLayout
爲Activity
的contentView
即可。
代碼如下:
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
| public class ElnReactBaseActivity extends BaseActivity { private ReactInstanceManager mReactInstanceManager; private ReactRootView mReactRootView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout linearLayout = new LinearLayout(this); linearLayout.setOrientation(LinearLayout.VERTICAL); viewStatusbarBackground = LayoutInflater.from(this).inflate(R.layout.transparent_status_bar_bg_view, linearLayout, false); linearLayout.addView(viewStatusbarBackground); mReactRootView = new ReactRootView(this); LinearLayout.LayoutParams layParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); linearLayout.addView(mReactRootView, layParams); mReactInstanceManager = ReactHelper.getInstance().getReactManager(); Bundle bundle = getExtra(); mReactRootView.startReactApplication(mReactInstanceManager, "ELearning", bundle); setContentView(linearLayout); } }
|
小米 與 魅族 與 (莫名其妙的)華爲
小米 與 魅族都能通過自各的反射方法實現狀態欄的亮色模式,解決白色Titlebar
的問題.
這點兩家做得很好.這裏直接給出官方文檔說明了.
小米狀態欄變色
魅族狀態欄變色
上述的代碼也整合進了GitHub中的工程TitlebarBelowTransparentStatusBar
.
至於華爲,額…大部分華爲機子都是好機,但華爲榮耀6 Plus(PE-TL10,EMUI3.1,Android 5.1.1)明明是Android 5.1,但使用5.1的代碼無效,得使用4.4的實現方式.
騰訊優測UTest
一個方便使用的App遠程測試平臺,機型多,Android版本齊全.
出了華爲這檔子事,就把App上傳試了下其它各種手機,還好還好,沒發現其它妖娥子.
About Sodino