控制状态栏背景色的源码
默认状态栏背景色
StatusBar.java类的源码中,start方法会调用makeStatusBarView方法,这个方法负责创建statusbar。意外发现状态栏的高度是读取com.android.internal.R.dimen.status_bar_height的值来设置的
public void refreshStatusBarHeight() {
int heightFromConfig = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
if (mBarHeight != heightFromConfig) {
mBarHeight = heightFromConfig;
apply(mCurrentState);
}
if (DEBUG) Log.v(TAG, "defineSlots");
}
我这台测试机是安卓R,所以应状态栏是黑色的是正确的,那问题就锁定在了状态栏图标为什么不是白色的了?
除了默认背景色,其实项目中用的更多的是外部设置状态栏背景色。
外部设置状态栏背景色
PhoneWindow.java类设置状态栏背景色的源码
@Override
public void setStatusBarColor(int color) {
mStatusBarColor = color;
mForcedStatusBarColor = true;
if (mDecor != null) {
mDecor.updateColorViews(null, false /* animate */);
}
final WindowControllerCallback callback = getWindowControllerCallback();
if (callback != null) {
getWindowControllerCallback().updateStatusBarColor(color);
}
}
DecorView.java中更新背景色的源码
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
final WindowInsetsController controller = getWindowInsetsController();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD;
if (!mWindow.mIsFloating || isImeWindow) {
boolean disallowAnimate = !isLaidOut();
disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
& FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
mLastWindowFlags = attrs.flags;
if (insets != null) {
final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars());
final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars());
mLastTopInset = systemBarInsets.top;
mLastBottomInset = systemBarInsets.bottom;
mLastRightInset = systemBarInsets.right;
mLastLeftInset = systemBarInsets.left;
// Don't animate if the presence of stable insets has changed, because that
// indicates that the window was either just added and received them for the
// first time, or the window size or position has changed.
boolean hasTopStableInset = stableBarInsets.top != 0;
disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
mLastHasTopStableInset = hasTopStableInset;
boolean hasBottomStableInset = stableBarInsets.bottom != 0;
disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
mLastHasBottomStableInset = hasBottomStableInset;
boolean hasRightStableInset = stableBarInsets.right != 0;
disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
mLastHasRightStableInset = hasRightStableInset;
boolean hasLeftStableInset = stableBarInsets.left != 0;
disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset);
mLastHasLeftStableInset = hasLeftStableInset;
mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars();
}
boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
calculateNavigationBarColor(), mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
ViewRootImpl vri = getViewRootImpl();
if (vri != null) {
vri.requestInvalidateRootRenderNode();
}
}
boolean statusBarNeedsRightInset = navBarToRightEdge
&& mNavigationColorViewState.present;
boolean statusBarNeedsLeftInset = navBarToLeftEdge
&& mNavigationColorViewState.present;
int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
: statusBarNeedsLeftInset ? mLastLeftInset : 0;
updateColorViewInt(mStatusColorViewState, sysUiVisibility,
calculateStatusBarColor(), 0, mLastTopInset,
false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
if (mHasCaption) {
final int captionColor = calculateStatusBarColor();
mDecorCaptionView.getCaption().setBackgroundColor(captionColor);
updateDecorCaptionShade();
}
}
// When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
// mForceWindowDrawsBarBackgrounds, we still need to ensure that the rest of the view
// hierarchy doesn't notice it, unless they've explicitly asked for it.
//
// Note: We don't need to check for IN_SCREEN or INSET_DECOR because unlike the status bar,
// these flags wouldn't make the window draw behind the navigation bar, unless
// LAYOUT_HIDE_NAVIGATION was set.
//
// Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
// consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
|| !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
&& (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
&& decorFitsSystemWindows
&& !hideNavigation)
|| (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
boolean consumingNavBar =
((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
&& decorFitsSystemWindows
&& !hideNavigation)
|| forceConsumingNavBar;
// If we didn't request fullscreen layout, but we still got it because of the
// mForceWindowDrawsBarBackgrounds flag, also consume top inset.
// If we should always consume system bars, only consume that if the app wanted to go to
// fullscreen, as othrewise we can expect the app to handle it.
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (attrs.flags & FLAG_FULLSCREEN) != 0
|| !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& decorFitsSystemWindows
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
&& mForceWindowDrawsBarBackgrounds
&& mLastTopInset != 0
|| (mLastShouldAlwaysConsumeSystemBars && fullscreen);
int consumedTop = consumingStatusBar ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
if (mContentRoot != null
&& mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
|| lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
lp.topMargin = consumedTop;
lp.rightMargin = consumedRight;
lp.bottomMargin = consumedBottom;
lp.leftMargin = consumedLeft;
mContentRoot.setLayoutParams(lp);
if (insets == null) {
// The insets have changed, but we're not currently in the process
// of dispatching them.
requestApplyInsets();
}
}
if (insets != null) {
insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
}
}
if (forceConsumingNavBar) {
mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset);
} else {
mBackgroundInsets = Insets.NONE;
}
updateBackgroundDrawable();
return insets;
}
这个方法中重点关注的是updateColorViewInt这个方法,看下它的实现
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
boolean animate, boolean force, WindowInsetsController controller) {
state.present = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
? state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force)
: state.attributes.isPresent(
controller.isRequestedVisible(state.attributes.insetsType),
mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
boolean showView = show && !isResizing() && !mHasCaption && size > 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
? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity)
: state.attributes.verticalGravity;
if (view == null) {
if (showView) {
state.view = view = new View(mContext);
setColor(view, color, dividerColor, verticalBar, seascape);
view.setTransitionName(state.attributes.transitionName);
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;
}
这个方法里面重点是setColor方法
private static void setColor(View v, int color, int dividerColor, boolean verticalBar,
boolean seascape) {
if (dividerColor != 0) {
final Pair<Boolean, Boolean> dir = (Pair<Boolean, Boolean>) v.getTag();
if (dir == null || dir.first != verticalBar || dir.second != seascape) {
final int size = Math.round(
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
v.getContext().getResources().getDisplayMetrics()));
// Use an inset to make the divider line on the side that faces the app.
final InsetDrawable d = new InsetDrawable(new ColorDrawable(color),
verticalBar && !seascape ? size : 0,
!verticalBar ? size : 0,
verticalBar && seascape ? size : 0, 0);
v.setBackground(new LayerDrawable(new Drawable[] {
new ColorDrawable(dividerColor), d }));
v.setTag(new Pair<>(verticalBar, seascape));
} else {
final LayerDrawable d = (LayerDrawable) v.getBackground();
final InsetDrawable inset = ((InsetDrawable) d.getDrawable(1));
((ColorDrawable) inset.getDrawable()).setColor(color);
((ColorDrawable) d.getDrawable(0)).setColor(dividerColor);
}
} else {
v.setTag(null);
v.setBackgroundColor(color);
}
}
所以排查状态栏图标显示不出来的问题,要综合考虑状态栏背景色 + 状态栏图标颜色两种情况来分析。只有全黑或者全白的或者图标隐藏的情况下,状态栏的图标才不可见。
下面分析下状态栏图标颜色是怎么设置的。
在StatusBar.java中start方法会调用onSystemBarAppearanceChanged,这个方法源码如下
@Override
public void onSystemBarAppearanceChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme) {
if (displayId != mDisplayId) {
return;
}
boolean barModeChanged = false;
if (mAppearance != appearance) {
mAppearance = appearance;
barModeChanged = updateBarMode(barMode(mTransientShown, appearance));
}
mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
mStatusBarMode, navbarColorManagedByIme);
updateBubblesVisibility();
}
这个方法主要调用了mLightBarController.onStatusBarAppearanceChanged方法,这个方法调用了onStatusBarModeChanged,然后这个方法又调用了updateStatus
private void updateStatus() {
final int numStacks = mAppearanceRegions.length;
int numLightStacks = 0;
// We can only have maximum one light stack.
int indexLightStack = -1;
for (int i = 0; i < numStacks; i++) {
if (isLight(mAppearanceRegions[i].getAppearance(), mStatusBarMode,
APPEARANCE_LIGHT_STATUS_BARS)) {
numLightStacks++;
indexLightStack = i;
}
}
// If all stacks are light, all icons get dark.
if (numLightStacks == numStacks) {
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
// If no one is light, all icons become white.
else if (numLightStacks == 0) {
mStatusBarIconController.getTransitionsController().setIconsDark(
false, animateChange());
}
// Not the same for every stack, magic!
else {
mStatusBarIconController.setIconsDarkArea(
mAppearanceRegions[indexLightStack].getBounds());
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
}
从这个方法可以看出,状态栏的图标有两种显示模式,一种是dark,一种是light。而显示哪一种模式取决于状态栏的mStatusBarMode,以及mAppearanceRegions这个数组中的每一个appearance的状态。
mStatusBarMode的值是通过调用barMode方法确定的
private static @TransitionMode int barMode(boolean isTransient, int appearance) {
final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
if (isTransient) {
return MODE_SEMI_TRANSPARENT;
} else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
return MODE_LIGHTS_OUT;
} else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) {
return MODE_LIGHTS_OUT_TRANSPARENT;
} else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) {
return MODE_OPAQUE;
} else {
return MODE_TRANSPARENT;
}
}
而这个方法有两个参数,其中第一个参数的值是由mTransientShown决定的,第二个参数是由mAppearance决定的。那么mTransientShown的值只有两个地方在修改,比较容易梳理。
private void clearTransient() {
if (mTransientShown) {
mTransientShown = false;
handleTransientChanged();
}
}
private void showTransientUnchecked() {
if (!mTransientShown) {
mTransientShown = true;
mNoAnimationOnNextBarModeChange = true;
handleTransientChanged();
}
}
而这两个方法就是隐藏、显示状态栏的方法。在我这个问题的背景下,肯定是显示状态栏的,所以这个值是true。
clearTransient方法是从AutoHideController.java中的hideTransientBars方法调用过来的,关键代码是mStatusBar.hide()方法。
showTransientUnchecked方法是从showTransient方法调过来的,而这个方法是来自CommandQueue的H类(MainLooper)的handleMessage方法。而CommandQueue是继承自IStatusBar.Stub,因此这次调用是Binder通信调过来的,具体怎么来的不细究了。
@Override
public void showTransient(int displayId, int[] types) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_SHOW_TRANSIENT, displayId, 0, types).sendToTarget();
}
}
case MSG_SHOW_TRANSIENT: {
final int displayId = msg.arg1;
final int[] types = (int[]) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showTransient(displayId, types);
}
break;
}
至此,mStatusBarMode的值是MODE_SEMI_TRANSPARENT,所以回到LightBarController.java中,updateStatus方法中,for循环中的isLight方法肯定是false,所以会走这个逻辑:If no one is light, all icons become white.
实际测试发现个别手机除外,确实是黑底白色图标。但是为什么1加8 安卓R上不是这样呢?