您的位置:首页 > 其它

10fitsSystemWindows对CoordinatorLayout的影响

2016-09-23 18:45 204 查看

10fitsSystemWindows对CoordinatorLayout的影响

提到过,为了让CollapsingToolbarLayout内部的伪状态栏和真正的statusbar重合, CoordinatorLayout和AppBarLayout的fitsSystemWindows应该一样,同时为true或者false。

现在来具体分析一下,各种case。对应Activity: CollapsFitSystemActivity

CoordinatorLayout和AppBarLayout的fitsSystemWindows都为true

此时CoordinatorLayout包含状态栏,AppBarLayout也包含状态,CollapsingToolbarLayout内的伪状态栏和实际状态栏在同样位置。

CoordinatorLayout的fitsSystemWindows为true,AppBarLayout的fitsSystemWindows为false

此时CoordinatorLayout包含状态栏,AppBarLayout不包含状态栏,会出现伪状态栏和实际状态栏不在一起,就显示了2条状态栏,

CoordinatorLayout的fitsSystemWindows为false,AppBarLayout的fitsSystemWindows为false

此时CoordinatorLayout不包括状态栏,AppBarLayout不包含状态栏,伪状态栏不会绘制。

为何CoordinatorLayout的fitsSystemWindows为false,CoordinatorLayout就不包括状态栏

CoordinatorLayout的fitsSystemWindows

这个值设置为true或者false,会有什么意义呢?为true,CoordinatorLayout就包括状态栏,否则就不包括状态栏。简单一句话,背后有一大堆代码来实现这个。

影响attachInfo.mSystemUiVisibility

首先看CoordinatorLayout构造函数里面有下边这段代码,setupForWindowInsets这明显就是处理系统边界的。

//CoordinatorLayout构造函数
if (INSETS_HELPER != null) {
INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
}


看INSETS_HELPER.setupForWindowInsets的具体实现,在CoordinatorLayoutInsetsHelperLollipop内,这里可以看出如果设置为true,那么就会加上View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN这个flag顾名思义代表全屏的意思。所以CoordinatorLayout 的mSystemUiVisibility内有SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN标志。

//CoordinatorLayoutInsetsHelperLollipop
public void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener) {
if (ViewCompat.getFitsSystemWindows(view)) {
// First apply the insets listener
ViewCompat.setOnApplyWindowInsetsListener(view, insetsListener);
// Now set the sys ui flags to enable us to lay out in the window insets
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}


而在view的performCollectViewAttributes代码里,把 attachInfo.mSystemUiVisibility |= mSystemUiVisibility;,所以attachInfo.mSystemUiVisibility里面也设置了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN标志。attachInfo是所有view共享的,从ViewRootImpl dispatch下来。

void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
if ((visibility & VISIBILITY_MASK) == VISIBLE) {
if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
attachInfo.mKeepScreenOn = true;
}
attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
attachInfo.mHasSystemUiListeners = true;
}
}
}


dispatchApplyInsets

DecorView的直接子view LinearLayout

ViewRootImpl在performTraversals时会调dispatchApplyInsets,内调DecorView的dispatchApplyWindowInsets,然后调DecorView的child的dispatchApplyWindowInsets,DecorView主要child是LinearLayout。所以会调用LinearLayout的dispatchApplyWindowInsets。LinearLayout的dispatchApplyWindowInsets会调用super.dispatchApplyWindowInsets。

//ViewGroup
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);
if (!insets.isConsumed()) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
insets = getChildAt(i).dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
break;
}
}
}
return insets;
}


所以调了view的dispatchApplyWindowInsets,会走L7 onApplyWindowInsets

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
try {
mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
} else {
return onApplyWindowInsets(insets);
}
} finally {
mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
}
}


这里走L6 fitSystemWindows,然后内部调用fitSystemWindowsInt,

public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
// We weren't called from within a direct call to fitSystemWindows,
// call into it as a fallback in case we're in a class that overrides it
// and has logic to perform.
if (fitSystemWindows(insets.getSystemWindowInsets())) {
return insets.consumeSystemWindowInsets();
}
} else {
// We were called from within a direct call to fitSystemWindows.
if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
return insets.consumeSystemWindowInsets();
}
}
return insets;
}


fitSystemWindowsInt里主要看 computeFitSystemWindows和internalSetPadding。

//View
private boolean fitSystemWindowsInt(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
Rect localInsets = sThreadLocal.get();
if (localInsets == null) {
localInsets = new Rect();
sThreadLocal.set(localInsets);
}
boolean res = computeFitSystemWindows(insets, localInsets);
mUserPaddingLeftInitial = localInsets.left;
mUserPaddingRightInitial = localInsets.right;
//根据localInsets设置padding
internalSetPadding(localInsets.left, localInsets.top,
localInsets.right, localInsets.bottom);
return res;
}
return false;
}


内调computeFitSystemWindows(此时this为DecorView的子view LinearLayout),看这里,这里的if有3个条件,我们可以简单点无视前面2个条件,这样就变为

((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0

&& !mAttachInfo.mOverscanRequested)

其实!mAttachInfo.mOverscanRequested一般为true,所以进一步简化条件变为(mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0

再看

public static final int SYSTEM_UI_LAYOUT_FLAGS =
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;


SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION肯定是false,所以我们只要看SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN就好了,条件进一步简化

mAttachInfo.mSystemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN==0

而由上文知,如果fitsSystemWindows 设置为true的话,mAttachInfo.mSystemUiVisibility 内会设置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,所以此时if不满足走下边else。

如果fitsSystemWindows设置为false,那就走if,把 outLocalInsets.set(inoutInsets);,这样上边的localInsets.top就是63(状态栏高度),在internalSetPadding内会设置LinearLayout的paddingTop为63,这样CoordinatorLayout就不可能包括状态栏了。

而如果CoordinatorLayout的fitsSystemWindows为true,那么下边会走else, LinearLayout的paddingTop也为0,所以CoordinatorLayout就包括状态栏了。

/**
* @hide Compute the insets that should be consumed by this view and the ones
* that should propagate to those under it.
*/
protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
|| mAttachInfo == null
|| ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
&& !mAttachInfo.mOverscanRequested)) {
//CoordinatorLayout的fitsSystemWindows为false
outLocalInsets.set(inoutInsets);
inoutInsets.set(0, 0, 0, 0);
return true;
} else {
//CoordinatorLayout的fitsSystemWindows为true
// The application wants to take care of fitting system window for
// the content...  however we still need to take care of any overscan here.
final Rect overscan = mAttachInfo.mOverscanInsets;
outLocalInsets.set(overscan);
inoutInsets.left -= overscan.left;
inoutInsets.top -= overscan.top;
inoutInsets.right -= overscan.right;
inoutInsets.bottom -= overscan.bottom;
return false;
}
}


fitsSystemWindows设为true和false的区别主要在于DecorView的子view LinearLayout的padding设置不同。所以第三层的FrameLayout的高度就不一样了。这就是CoordinatorLayout是否包含状态栏的原因。

为何CoordinatorLayout的fitsSystemWindows为false,AppBarLayout的fitsSystemWindows为false,不会出现双状态栏呢?

首先我们要看看CollapsingToolbarLayout的伪状态栏绘制有什么前提,必须topInset>0, mLastInsets不为null。那mLastInsets是什么时候赋值的呢?

//android.support.design.widget.CollapsingToolbarLayout#draw
// Now draw the status bar scrim
if (mStatusBarScrim != null && mScrimAlpha > 0) {
final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
if (topInset > 0) {
mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
topInset - mCurrentOffset);
mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
mStatusBarScrim.draw(canvas);
}
}


构造函数里注册了个回调,这是在dispatchApplyInsets调用的,必须调到CollapsingToolbarLayout的dispatchApplyInsets才可能有mLastInsets。

//CollapsingToolbarLayout构造函数
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
mLastInsets = insets;
requestLayout();
return insets.consumeSystemWindowInsets();
}
});


在ViewRootImpl的dispatchApplyInsets过程中,会发给DecorView的直接子view,LinearLayout,如果CoordinatorLayout的fitsSystemWindows为false,那么 LinearLayout在L3会消耗掉这个inset,直接返回true,而不会继续往

LinearLayout的子view分发,所以CollapsingToolbarLayout内的mLastInsets必定为空,那伪状态栏根本不会绘制。

@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);
if (!insets.isConsumed()) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
insets = getChildAt(i).dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
break;
}
}
}
return insets;
}


总结

1、CoordinatorLayout的fitsSystemWindows为false,那CollapsingToolbarLayout就必定不会绘制伪状态栏,因为mLastInsets为null。

参考资料

https://my.oschina.net/KobeGong/blog/469059
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: