前言
状态栏与导航栏属于SystemUi的管理范畴,虽然界面的UI会受到SystemUi的影响,但是,APP并没有直接绘制SystemUI的权限与必要。APP端之所以能够更改状态栏的颜色、导航栏的颜色,其实还是操作自己的View更改UI。可以这么理解:状态栏与导航栏拥有自己独立的窗口,而且这两个窗口的优先级较高,会悬浮在所有窗口之上,可以把系统自身的状态栏与导航栏看做全透明的,之所有会有背景颜色,是因为下层显示界面在被覆盖的区域添加了颜色,之后,通过SurfaceFlinger的图层混合,好像是状态栏、导航栏自身有了背景色。看一下一个普通的Activity展示的时候,所对应的Surface(或者说Window也可以)。
- 第一个XXXXActivity,大小是屏幕大小
- 第二个状态栏StatusBar,大小对应顶部那一条
- 第三个是底部虚拟导航栏NavigationBar,大小对应底部那一条
- HWC_FRAMEBUFFER_TARGET:是合成的目标Layer,不参与合成
从上表可以看出,虽然只展示了一个Activity,但是同时会有StatusBar、NavigationBar、XXXXActivity可以看出Activity是在状态栏与导航栏下面的,被覆盖了,它们共同参与显示界面的合成,但是,StatusBar、NavigationBar明显不是属于APP自身UI管理的范畴。下面就来分析一下,APP层的API如何影响SystemUI的显示的,并一步步解开所谓沉浸式与全屏的原理,首先看一下如何更改状态栏颜色。
一、状态栏、导航栏和Activity布局的演变
1、Android 5.0之前activity默认是在statusbar下边,navigationbar上边,而在Android5.0开始,activity真正的全屏,只不过内容布局还会空出statusbar,navigationbar空间(除非设置了SYSTEM_UI_FLAG_xx),statusbar和navigationbar处加入了有颜色的view。Android在API 21的时候为Window添加了setNavigationBarColor、setStatusBarColor,进一步提升SystemBar用户体验。PhoneWindow继承Window具体实现了setNavigationBarColor、setStatusBarColor,具体代码如下:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
int mStatusBarColor = 0;
int mNavigationBarColor = 0;
int mNavigationBarDividerColor = 0;
public void setStatusBarColor(int color) {
mStatusBarColor = color;
mForcedStatusBarColor = true;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
}
}
public void setNavigationBarColor(int color) {
mNavigationBarColor = color;
mForcedNavigationBarColor = true;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
mDecor.updateNavigationGuardColor();
}}
}
}
不难发现主要是DecorView的updateColorViews在work,通过查看代码,可以明白是DecorView在SystemBar的位置add了对应的ColorStateView,这个有点类似PhoneWindowManager里边的WindowState,之后对ColotStateView里边的view进行操作即可,比如说setBackground来改变其颜色。
二、DecorView更新状态栏和导航栏背景颜色的流程分析
1、假设当前的场景是默认样式的Activity,如果想要更新状态栏颜色只需要如下代码:
getWindow().setStatusBarColor(RED);
2、其实这里调用的是PhoneWindow的setStatusBarColor函数,无论是Activity还是Dialog都是被抽象成PhoneWindow:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
int mStatusBarColor = 0;
@Override
public void setStatusBarColor(int color) {
mStatusBarColor = color;
mForcedStatusBarColor = true;
if (mDecor != null) {
mDecor.updateColorViews(null, false);
}
}
}
3、PhoneWindow的setStatusBarColor方法最终调用的是DecorView的updateColorViews函数,DecorView是属于Activity的PhoneWindow的内部对象,也就说,更新的对象从所谓的Window进入到了Activity自身的布局视图中,接着看DecorView的updateColorViews方法:
frameworks/base/core/java/com/android/internal/policy/DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//状态栏视图属性对象
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.statusBarBackground,
FLAG_FULLSCREEN);
//导航栏视图属性对象
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(
SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.navigationBarBackground,
0 /* hideWindowFlag */);
private final ColorViewState mStatusColorViewState =
new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES);
private final ColorViewState mNavigationColorViewState =
new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES);
private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
final boolean isImeWindow =
mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD;
if (!mWindow.mIsFloating || isImeWindow) {
...代码省略...
boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
//更新导航栏颜色,mNavigationColorViewState为导航栏的相关筛选条件
updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
mWindow.mNavigationBarColor, mWindow.mNavigationBarDividerColor, navBarSize/*导航栏高度*/,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate, false /* force */);
boolean statusBarNeedsRightInset = navBarToRightEdge
&& mNavigationColorViewState.present;
boolean statusBarNeedsLeftInset = navBarToLeftEdge
&& mNavigationColorViewState.present;
int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
: statusBarNeedsLeftInset ? mLastLeftInset : 0;
//更新状态栏颜色,mStatusColorViewState为状态栏的相关筛选条件
updateColorViewInt(mStatusColorViewState, sysUiVisibility,
calculateStatusBarColor(), 0, mLastTopInset/*状态栏高度*/,
false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
animate && !disallowAnimate,
mForceWindowDrawsStatusBarBackground);
}
...代码省略...
}
}
DecorView的updateColorViews方法会调用updateColorViewInt方法依次对导航栏背景颜色对象mNavigationColorViewState和状态栏背景颜色对象mStatusColorViewState进行视图更新。
4、updateColorViewInt方法如下所示:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size/*状态栏或导航栏的高度*/, boolean verticalBar/*是否是竖直屏*/, boolean seascape, int sideMargin,
boolean animate, boolean force) {
//SystemUI视图是否存在
state.present = state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force);
//SystemUI视图内容是否可见
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
...代码省略...
}
}
updateColorViewInt方法首先会调用ColorViewAttributes的isPresent方法判断对应的SystemUI视图是否存在,并将结果赋值给state.present。
public static class ColorViewAttributes {
public boolean isPresent(int sysUiVis, int windowFlags, boolean force) {
//状态栏(SYSTEM_UI_FLAG_FULLSCREEN)、导航栏(SYSTEM_UI_FLAG_HIDE_NAVIGATION)
return (sysUiVis & systemUiHideFlag) == 0
//状态栏(FLAG_TRANSLUCENT_STATUS)、导航栏(FLAG_TRANSLUCENT_NAVIGATION)
&& (windowFlags & hideWindowFlag) == 0
//状态栏(STATUS_BAR_BACKGROUND_TRANSITION_NAME)、导航栏(NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME)
&& ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
|| force);
}
}
然后会继续调用ColorViewAttributes的isVisible方法判断视图内容是否显示,并将结果赋值给布尔类型的show变量。
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
//SystemUI视图是否存在
return present
//如果背景是透明色,则没必要添加背景颜色
&& (color & Color.BLACK) != 0
//背景色与translucent半透明效果互斥,半透明就统一用半透明颜色,不会再添加额外颜色。
&& ((windowFlags & translucentFlag) == 0 || force);
}
5、继续往下看DecorView的updateColorViewInt方法。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
boolean animate, boolean force) {
//SystemUI视图是否存在
state.present = state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force);
//SystemUI视图内容是否可见
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
//是否显示View
boolean showView = show && !isResizing() && size > 0;
if (view == null) {
if (showView) {
//如果状态栏、导航栏占位视图不存在则创建占位视图
state.view = view = new View(mContext);
//设置颜色
setColor(view, color, dividerColor, verticalBar, seascape);
//设置转换名称
view.setTransitionName(state.attributes.transitionName);
//设置id
view.setId(state.attributes.id);
//窗口可见性发生了改变
visibilityChanged = true;
view.setVisibility(INVISIBLE);
state.targetVisibility = VISIBLE;
LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
resolvedGravity);
if (seascape) {
lp.leftMargin = sideMargin;
} else {
lp.rightMargin = sideMargin;
}
//将新创建的占位视图添加到当前窗口视图内容中
addView(view, lp);
updateColorViewTranslations();
}
} else {
int vis = showView ? VISIBLE : INVISIBLE;
visibilityChanged = state.targetVisibility != vis;
state.targetVisibility = vis;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int rightMargin = seascape ? 0 : sideMargin;
int leftMargin = seascape ? sideMargin : 0;
if (lp.height != resolvedHeight || lp.width != resolvedWidth
|| lp.gravity != resolvedGravity || lp.rightMargin != rightMargin
|| lp.leftMargin != leftMargin) {
lp.height = resolvedHeight;
lp.width = resolvedWidth;
lp.gravity = resolvedGravity;
lp.rightMargin = rightMargin;
lp.leftMargin = leftMargin;
view.setLayoutParams(lp);
}
if (showView) {
setColor(view, color, dividerColor, verticalBar, seascape);
}
}
if (visibilityChanged) {
//如果状态栏、导航栏视图可见性发生了变化,添加动画
view.animate().cancel();
if (animate && !isResizing()) {
if (showView) {
if (view.getVisibility() != VISIBLE) {
view.setVisibility(VISIBLE);
view.setAlpha(0.0f);
}
view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
setDuration(mBarEnterExitDuration);
} else {
view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
.setDuration(mBarEnterExitDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
state.view.setAlpha(1.0f);
state.view.setVisibility(INVISIBLE);
}
});
}
} else {
view.setAlpha(1.0f);
view.setVisibility(showView ? VISIBLE : INVISIBLE);
}
}
state.visible = show;
state.color = color;
}
}
三、DecorView更新导航栏背景颜色
1、导航栏背景颜色对象
public static final String NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME =
"android:navigation:background";
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(
SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.navigationBarBackground,
0 /* hideWindowFlag */);
private final ColorViewState mNavigationColorViewState =
new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES);
2、对于ColorViewAttributes的isPresent方法来说:
public static class ColorViewAttributes {
public boolean isPresent(int sysUiVis, int windowFlags, boolean force) {
//如果当前窗口的sysUiVis中包含SYSTEM_UI_FLAG_HIDE_NAVIGATION属性,则隐藏导航栏。
return (sysUiVis & systemUiHideFlag) == 0
//如果当前窗口的样式windowFlags中包含FLAG_TRANSLUCENT_NAVIGATION属性,则隐藏导航栏。
&& (windowFlags & hideWindowFlag) == 0
//如果当前窗口的样式windowFlags中不包含FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS属性,也就是绘制导航栏背景,则隐藏导航栏。
&& ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
//是否强制导航栏存在,默认不强制
|| force);
}
}
3、对于ColorViewAttributes的isVisible方法来说:
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
//当前窗口的SystemUI视图存在,则显示导航栏视图内容
return present
//如果导航栏背景不是透明色,则显示导航栏视图内容
&& (color & Color.BLACK) != 0
//如果导航栏窗口半透明,则显示导航栏视图内容
&& ((windowFlags & translucentFlag) == 0
//是否强制显示导航栏视图,默认不强制
|| force);
}
四、DecorView更新状态栏背景颜色
1、状态栏背景颜色对象
public static final String STATUS_BAR_BACKGROUND_TRANSITION_NAME = "android:status:background";
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.statusBarBackground,
FLAG_FULLSCREEN);
private final ColorViewState mStatusColorViewState =
new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES);
2、对于ColorViewAttributes的isPresent方法来说:
public static class ColorViewAttributes {
public boolean isPresent(int sysUiVis, int windowFlags, boolean force) {
//如果当前窗口的sysUiVis中包含SYSTEM_UI_FLAG_FULLSCREEN属性,则隐藏状态栏。
return (sysUiVis & systemUiHideFlag) == 0
//如果当前窗口的样式windowFlags中包含FLAG_TRANSLUCENT_STATUS属性,则隐藏状态栏。
&& (windowFlags & hideWindowFlag) == 0
//如果当前窗口的样式windowFlags中不包含STATUS_BAR_BACKGROUND_TRANSITION_NAME属性,也就是绘制状态栏背景,则隐藏状态栏。
&& ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
//是否强制状态栏存在,默认不强制
|| force);
}
}
3、对于ColorViewAttributes的isVisible方法来说:
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
//当前窗口的SystemUI视图存在,则显示状态栏视图内容
return present
//如果状态栏背景不是透明色,则显示状态栏视图内容
&& (color & Color.BLACK) != 0
//如果状态栏窗口半透明,则显示状态栏视图内容
&& ((windowFlags & translucentFlag) == 0
//是否强制显示状态栏视图,默认不强制
|| force);
}
💡 技术无价,赞赏随心
写文不易,如果本文帮你避开了“八小时踩坑”,或者让你直呼“学到了!”
欢迎扫码赞赏,让我知道这篇内容值得!