在Android开发中我们越来越重视用户的App操作体验,在使用App中我们主张减少对用户的干扰,经常会提到一致性体验。为了追求界面的风格的一致性,Google官方在Android 4.4 开始,支持了系统最上方的状态栏(StatusBar)和最下方的导航栏(Navigation Bar)可以被透明化,使得APP中的设计可以过渡更加平滑,不像之前那样的割裂感,让整个APP更加一致。而且后续的系统版本中,持续增加了对状态栏操作的api接口,但这也导致了如果直接使用4.4的方法在Android 5.0 以上会导致显示效果不一致的问题。为了避免这个情况,那么开发者需要考虑版本的兼容性,对不同的系统版本进行分别处理。下面将从不同的系统版本中介绍如何实现沉浸式状态栏的效果。
代码实例:沉浸式状态栏
在设置沉浸式状态栏时,首先要将对应Activity设置一个主题(theme),该主题直接继承Theme.AppCompat.Light.NoActionBar,然后直接在主题中设置状态栏透明:
....
.....
当直接使用如上设置时,细心的你可能会发现一个问题:本来在状态栏下面显示的文字,尽然都跑到状态栏中去了而且与状态栏中信息发生重叠。你一看,感觉这操作还不如刚才呢。别急,系统早已为我们提供了一个解决方案,那就是在主题中补充一个android:fitsSystemWindows = true 的属性或者加在Activity对应的布局文件的根属性上。这样就避免了这个问题。
该属性的作用是设置为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;
}
对于在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}.
*
* The transitionName for the view background will be "android:status:background".
*
*/
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:设置全屏
在设置全局显示时,我们同样既可以通过主题设置,也可以通过代码动态设置:
这里我们主要分析代码设置的方法:
/**
* 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的区别)
在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.
*
* 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;
}