fitsSystemWindows 属性
该属性
只影响当前 View 对状态栏的适配
该属性为 true 时,View 会自己适配状态栏,即给自己添加一个 paddTop,它的子 View 就不会被状态栏所遮挡,然后适配状态栏事件到此为止不会传递给它的子 View。
该属性为 false 时,View 会将事件传递给所有子 View,由子 View 自己处理。注意:子 View 无论消费不消费该事件都不会阻止它的兄弟 View 得到事件,它们能否得到事件完全取决于父 View。
原理
view 的三大流程起点都在 ViewRootImpl#performTraversals() 中,在执行三大流程前,会先调用 View#dispatchApplyWindowInsets()。所以首先得到事件的一定是 DecorView,DecorView 没有重写该方法,所以执行到 ViewGroup 中
// ViewGroup.java
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
// 该方法就是看自己是不是消费事件
insets = super.dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) { // 如果消耗整个事件结束
return insets;
}
if (View.sBrokenInsetsDispatch) {
// android 32 时 if 判断不成立
return brokenDispatchApplyWindowInsets(insets);
} else {
return newDispatchApplyWindowInsets(insets);
}
}
private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
// 遍历所有子 View。可以看到子 view 无论是否消耗该事件都不影响它的兄弟
getChildAt(i).dispatchApplyWindowInsets(insets);
}
return insets;
}
View 的处理流程也比较简单:添加 padding 或 margin 或根据位置在 layout 时将偏移子 View 位置。
// View.java
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
try {
mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
// 如果调用 setOnApplyWindowInsetsListener() 设置过监听,就走自己的回调
// 否则执行默认逻辑
return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
} else {
return onApplyWindowInsets(insets);
}
} finally {
mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
}
}
onApplyWindowInsets() 内部虽然分了几个分支,但最终会到 applyInsets(),该方法就会设置 paddingTop,实现对状态栏的适配。
涉及方法
- dispatchApplyWindowInsets:整个事件的适配入口
- setOnApplyWindowInsetsListener:设置事件监听,它的优先级高于 onApplyWindowInsets
- onApplyWindowInsets:用于当前 View 对状态栏的适配
viewpager+fragment
-
vp 自定义 windowInsets 的分发。旧版本时, windowInsets 只分发给第一个设置了 fitsSystemWindows = true 的子 view,这里为了处理多个 frg 就需要自定义分发逻辑。因为自定义了分发逻辑,所以 vp 的 fitsSystemWindows 配置成 true/false 都没有影响。// demo 版,描述大致意思 vp.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { int childCount = vp.getChildCount(); for (int i = 0; i < childCount; i++) { // 遍历所有的 view,依次分发 vp.getChildAt(i).dispatchApplyWindowInsets(insets); } return insets.consumeSystemWindowInsets(); } }); -
对于 frg:
背景需要延伸到状态栏下,内容又不能延伸过去。也就是说 frg#view 应包括状态栏部分,布局子 view 时需额外添加 padding,避免状态栏覆盖子 view。这正是 CoordinatorLayout 的默认实现逻辑。所以frg 使用 CoordinatorLayout 作为根 view 且 fitsSystemWindows = true。可以自定义实现自己的业务。 -
应用 theme 中可配置两个属性。第一个保证能延伸至状态栏下,第二个保证能延伸至底部虚拟按键下
<item name="android:windowTranslucentStatus">true</item> <item name="android:windowTranslucentNavigation">true</item>
最终整体效果如下,左右两边是两个不同的 frg