前言
状态栏与导航栏属于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的显示的,并一步步解开所谓沉浸式与全屏的原理,首先看一下如何更改状态栏颜色。
一、状态栏颜色更新原理
1、假设当前的场景是默认样式的Activity,如果想要更新状态栏颜色只需要如下代码:
getWindow().setStatusBarColor(RED);
2、其实这里调用的是PhoneWindow的setStatusBarColor函数,无论是Activity还是Dialog都是被抽象成PhoneWindow:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
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、最终调用的是DecorView的updateColorViews函数,DecorView是属于Activity的PhoneWindow的内部对象,也就说,更新的对象从所谓的Window进入到了Activity自身的布局视图中,接着看DecorView,这里只关注更改颜色:
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);
private final ColorViewState mStatusColorViewState =
new ColorViewState(STATUS_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 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);
}
...代码省略...
}
}
4、mStatusColorViewState其实就代表StatusBar的背景颜色对象,主要属性包括显示的条件以及颜色值:
private final ColorViewState mStatusColorViewState = new ColorViewState(
SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
Gravity.TOP,
Gravity.LEFT,
STATUS_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.statusBarBackground,
FLAG_FULLSCREEN);
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);
如果当前对应Window的SystemUi设置了SYSTEM_UI_FLAG_FULLSCREEN后,就会隐藏状态栏,那就不在需要为状态栏设置背景,否则就设置:
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int size, boolean verticalBar, int rightMargin, boolean animate) {
<!--关键点1 条件1-->
state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
&& (getAttributes().flags & state.hideWindowFlag) == 0
&& (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
<!--关键点2 条件2-->
boolean show = state.present
&& (color & Color.BLACK) != 0
&& (getAttributes().flags & state.translucentFlag) == 0;
boolean visibilityChanged = false;
View view = state.view;
int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
if (view == null) {
if (show) {
state.view = view = new View(mContext);
view.setBackgroundColor(color);
view.setTransitionName(state.transitionName);
view.setId(state.id);
visibilityChanged = true;
view.setVisibility(INVISIBLE);
state.targetVisibility = VISIBLE;
<!--关键点3-->
LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
resolvedGravity);
lp.rightMargin = rightMargin;
addView(view, lp);
updateColorViewTranslations();
}}
...}
先看下关键点1跟2 ,这里是根据SystemUI的配置决定是否显示状态栏背景颜色,如果状态栏都不显示,那就没必要显示背景色了,其次,如果状态栏显示,但背景是透明色,也没必要添加背景颜色,即不满足(color & Color.BLACK) != 0。最后看一下translucentFlag,默认情况下,状态栏背景色与translucent半透明效果互斥,半透明就统一用半透明颜色,不会再添加额外颜色。最后,再来看关键点3,其实很简单,就是往DecorView上添加一个View,原则上说DecorView也是一个FrameLayout,所以最终的实现就是在FrameLayout添加一个有背景色的View。