在Android開發中我們越來越重視使用者的App操作體驗,在使用App中我們主張減少對使用者的幹擾,經常會提到一緻性體驗。為了追求界面的風格的一緻性,Google官方在Android 4.4 開始,支援了系統最上方的狀态欄(StatusBar)和最下方的導航欄(Navigation Bar)可以被透明化,使得APP中的設計可以過渡更加平滑,不像之前那樣的割裂感,讓整個APP更加一緻。而且後續的系統版本中,持續增加了對狀态欄操作的api接口,但這也導緻了如果直接使用4.4的方法在Android 5.0 以上會導緻顯示效果不一緻的問題。為了避免這個情況,那麼開發者需要考慮版本的相容性,對不同的系統版本進行分别處理。下面将從不同的系統版本中介紹如何實作沉浸式狀态欄的效果。
代碼執行個體:沉浸式狀态欄
4.4(KITKAT)
在設定沉浸式狀态欄時,首先要将對應Activity設定一個主題(theme),該主題直接繼承Theme.AppCompat.Light.NoActionBar,然後直接在主題中設定狀态欄透明:
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBar">
....
</activity>
.....
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
<!-- 設定系統Status Bar顔色透明 -->
<item name="android:windowTranslucentStatus">true</item>
<!-- 設定系統Navigation Bar顔色透明 -->
<item name="android:windowTranslucentNavigation">true</item>
</style>
當直接使用如上設定時,細心的你可能會發現一個問題:本來在狀态欄下面顯示的文字,盡然都跑到狀态欄中去了而且與狀态欄中資訊發生重疊。你一看,感覺這操作還不如剛才呢。别急,系統早已為我們提供了一個解決方案,那就是在主題中補充一個android:fitsSystemWindows = true 的屬性或者加在Activity對應的布局檔案的根屬性上。這樣就避免了這個問題。
fitSystemWindows屬性:
該屬性的作用是設定為true時,可以避免應用内容和系統的視窗(statusbar)發生重疊,通過在View上設定和系統視窗一樣高度的邊框(padding)來確定應用内容不會出現到系統視窗中。這樣使得系統會自己計算好布局距狀态欄的高度,使界面内容布局位于狀态欄下方和導航欄上方。
如果一個布局中的多個view都設定了android:fitsSystemWindows="true"的屬性,那麼隻有第一個View會生效,其他view的設定無效。而且這個View中,再設定android:padding屬性會失效。
代碼實作
那麼一切按照如上配置操作,我們的确可以在4.4中實作狀态欄的透明效果,但是并不能随心所欲的達到自定義設定狀态欄顔色的效果,而且不具有我們所提倡的插拔式體驗。是以我們從代碼層面尋求更好的設計。
我們同樣可以通過代碼來實作windowTranslucentStatus 的效果,如下:
public void setTranslucentStatus(Activity activity, boolean on) {
Window win = activity.getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
因為我們在4.4中不能直接修改狀态欄的顔色,是以可以通過建立一個View然後設定為系統狀态欄同樣的高度,接着将它置于DecorView視窗的頂部将真正的狀态欄覆寫,這樣就可以改變這個View的背景色來實作狀态欄顔色改變。然後設定根布局fitSystemWindows,這樣就實作了4.4中的沉浸式狀态欄。
public void compat(Activity activity, int statusColor) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window win = activity.getWindow();
setTranslucentStatus(activity,true);
ViewGroup decorView = (ViewGroup) win.getDecorView();
decorView.addView(createStatusBarView(activity, statusColor));
setRootView(activity, true);
}
}
/**
* create a view which it's height equal system status bar's height
*
* @param context
* @param color
* @return
*/
private View createStatusBarView(Context context, @ColorInt int color) {
View statusBarView = new View(context);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams
(FrameLayout.LayoutParams.MATCH_PARENT, getStatusBarHeight(context));
params.gravity = Gravity.TOP;
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(color);
return statusBarView;
}
/**
* sets whether or not the root view of layout fitSystemWindows.
*
* @param activity
* @param fitSystemWindows
*/
private void setRootView(Activity activity, boolean fitSystemWindows) {
ViewGroup parent = activity.findViewById(Window.ID_ANDROID_CONTENT);
for (int i = 0; i < parent.getChildCount(); i++) {
View childView = parent.getChildAt(i);
if (childView instanceof ViewGroup) {
childView.setFitsSystemWindows(fitSystemWindows);
((ViewGroup) childView).setClipToPadding(fitSystemWindows);
}
}
}
/**
* receive the status bar height of the system.
*
* @param context
* @return
*/
private 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;
}
5.0(LOLLIPOP)
對于在4.4系統上,我們需要做的很多但是效果卻很少,所幸的是隻要Android還在發展低版本系統所占的份額隻會越來越少以至慢慢被淘汰。而Google官方也已經意識到對開發者開放狀态欄操作接口是必要的,是以在5.0的版本上,開發者無需做過多操作就可以直接修改狀态欄顔色。
Google直接在Window類中提供了setStatusBarColor方法:
public abstract class Window {
.....
/**
* Sets the color of the status bar to {@code color}.
*
* For this to take effect,
* the window must be drawing the system bar backgrounds with
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
*
* If {@code color} is not opaque, consider setting
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
* <p>
* The transitionName for the view background will be "android:status:background".
* </p>
*/
public abstract void setStatusBarColor(@ColorInt int color);
}
1:設定狀态欄顔色
方法中注釋中說明了,在設定狀态欄顔色的同時,還需要同步設定WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS這個Window Flag,并且需要保證WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS這個Window Flag沒有被設定。否則,不會生效。
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(statusColor);
2:設定狀态欄透明
如果我們需要實作狀态欄的透明效果同時上浮在布局内容上方,我們可以考慮這樣做:
/**
* set status bar transparent .
*
* @param activity
*/
public void setStatusBarTransparent(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = activity.getWindow();
View decorView = window.getDecorView();
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
window.setStatusBarColor(Color.TRANSPARENT);
decorView.setSystemUiVisibility(option);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setTranslucentStatus(activity, true);
}
}
/**
* set status bar translucent or not.
*
* @param activity
* @param on
*/
private void setTranslucentStatus(Activity activity, boolean on) {
Window win = activity.getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
注:
這裡出現了setSystemUiVisibility中兩個View的标記:
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN:視圖内容延伸至狀态欄區域,狀态欄上浮于視圖之上。這時再配合View.SYSTEM_UI_FLAG_LAYOUT_STABLE 标記,就能夠在status bar隐藏和顯示時,内容區域不會改變大小,進而保證布局的穩定。是以這兩者經常聯合使用。
3:設定全屏
在設定全局顯示時,我們同樣既可以通過主題設定,也可以通過代碼動态設定:
<style name="AppTheme.FullScreen" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowFullscreen">true</item>
</style>
這裡我們主要分析代碼設定的方法:
/**
* set systemUI hide or not
*
* @param activity
*/
public void setFullScreen(Activity activity, boolean fullScreen) {
if (fullScreen) {
Window window = activity.getWindow();
View decorView = window.getDecorView();
// Set the IMMERSIVE flag.
// Set the content to appear under the system bars so that the content
// doesn't resize when the system bars hide and show.
int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(option);
} else {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION:視圖延伸至導航欄區域,導航欄上浮于視圖之上;
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION:暫時隐藏導航欄, 由于導航欄的重要性,當與使用者互動後,比如單擊螢幕,都可能會導緻navigation bar重新出現,源于系統clear掉該标志與SYSTEM_UI_FLAG_FULLSCREEN 标志,同SYSTEM_UI_FLAG_IMMERSIVE 标志一起使用可避免被clear;
View.SYSTEM_UI_FLAG_FULLSCREEN:隐藏狀态欄,效果同設定WindowManager.LayoutParams.FLAG_FULLSCREEN;
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY:沉浸式效果,當使用者在系統欄區域向内滑動時,系統欄會顯示幾秒鐘然後重新消失;
View.SYSTEM_UI_FLAG_IMMERSIVE:沉浸式效果,當使用者在系統欄區域向内滑動時,系統欄會重新顯示并保持可見;(注意與STICKY的差別)
6.0(M)
在Android 6.0中,系統又提供新的方法來改變狀态欄中的字型顔色,這樣便能夠更好的适應系統狀态欄背景色。通過setSystemUiVisibility方法設定 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 可以自行修改狀态欄中字型為黑色或者白色。
看API中文檔對此的解釋有:
/**
* Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
* is compatible with light status bar backgrounds.
*
* <p>For this to take effect, the window must request
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
* FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
* FLAG_TRANSLUCENT_STATUS}.
*
* @see android.R.attr#windowLightStatusBar
*/
public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
提示了在繪制狀态欄背景色時,它可以相容light的模式。而且同樣需要設定WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS這個Window Flag,并且需要保證WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS這個Window Flag沒有被設定。否則,不會生效。
計算狀态欄背景色的light和dark,可以使用系統提供的方法,是以進行如下設定:
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setStatusBarColor(statusColor);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
if (decorView != null) {
int vis = decorView.getSystemUiVisibility();
if (isLightColor(statusColor)) {
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; //black
} else {
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; //white
}
decorView.setSystemUiVisibility(vis);
}
}
/**
* calculate the color is light or dark.
*
* @param color
* @return
*/
private boolean isLightColor(@ColorInt int color) {
return ColorUtils.calculateLuminance(color) >= 0.5;
}