在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);
//don't work
// View decorView = window.getDecorView();
// int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
// | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
// decorView.setSystemUiVisibility(option);
}
}
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;
}