fitsSystemWindows这个属性最开始出现的地方是关于沉浸式状态栏的时候,用法也是很简单粗暴,在xml的根布局设置这个属性为true,你所要的内容就适配了屏幕,我们来看下文档中对他的解释:
Boolean internal attribute to adjust view layout based on system windows such as the status bar. If true, adjusts the padding of this >view to leave space for the system windows. Will only take effect if this view is in a non-embedded activity.
经过具体实验,具体的作用就是你的contentview是否忽略actionbar,title,屏幕的底部虚拟按键,将整个屏幕当作可用的空间。
正常情况,contentview可用的空间是去除了actionbar,title,底部按键的空间后剩余的可用区域;
但有时候写了此属性会不生效,所以我们由此来深入的分析一下:
首先要从viewRootimp的performTraversals说起,也是任何绘制的由来,里面有个dispatchApplyInsets方法
void dispatchApplyInsets(View host) {
host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}
//view的dispatchApplyWindowInsets方法
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;
}
}
//viewgroup的dispatchApplyWindowInsets方法
@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;
}
首先这个host当然就是decorview了,首先调用view的dispatchApplyWindowInsets方法,当没有mOnApplyWindowInsetsListener时调用自己的onApplyWindowInsets方法也就是
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;
}
这里一个PFLAG3_FITTING_SYSTEM_WINDOWS这个flag由于已经被ActionBarOverlayLayout设置过了,所以直接走的下面这个方法
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;
internalSetPadding(localInsets.left, localInsets.top,
localInsets.right, localInsets.bottom);
return res;
}
return false;
}
对于设置FITS_SYSTEM_WINDOWS的view做了以下判断,毕竟有很多view设置了这个属性,怎么判断被消耗了呢,就要走到
computeFitSystemWindows方法里了
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)) {
outLocalInsets.set(inoutInsets);
inoutInsets.set(0, 0, 0, 0);
return true;
} else {
// 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;
}
}
看到了设置padding的关键代码可以看到几个判断,简单来说如果我们在xml的根部view设置了fitSystemWindows这个属性或者是非全屏条件下设置了这个属性的view。
简单来说就是
1.设置了全屏后只有xml的根view才能让fitsystemwindow的属性生效
即自动调整布局
2.不设置全屏,只要设置了fitsystemwindow的view就能消耗此属性,在decoview添加的view中 com.android.internal.R.layout.screen_simple就是被率先添加的
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
可以看到在content外面的一层的Linearlayout就写了此属性,消费的意义就是LinearLayout设置了paddingTop为25dp,避开了状态栏,而LinearLayout的子view FrameLayout会比LinearLayout少了25dp.
网友评论