开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
当我第一次处理Insets时,第一次了解到了onApplyWindowInsets这个函数。 我这样重写它时,发现日志并没有打印出来。
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
System.out.println("onApplyWindowInsets");
return super.onApplyWindowInsets(insets);
}
在ViewRootImpl里面我发现了这样一段代码
//第一次执行perFormTraversals
if(mFirst)
{
.....
//host指的是DecorView
dispatchApplyInsets(host);
}
public void dispatchApplyInsets(View host) {
.....
WindowInsets insets = getWindowInsets(true /* forceConstruct */);
host.dispatchApplyWindowInsets(insets);
}
后面又来到了ViewGroup中
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
return insets;
}
if (View.sBrokenInsetsDispatch) {
return brokenDispatchApplyWindowInsets(insets);
} else {
return newDispatchApplyWindowInsets(insets);
}
}
这里我们可以看到如果insets.isConsumed()被当前View消费了,那么就不继续分发了。
向子view分发的过程是后面的brokenDispatchApplyWindowInsets和newDispatchApplyWindowInsets。
那么是在哪里消耗的呢?
看一下super.dispatchApplyWindowInsets
现在来到了View.java
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
try {
mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
.....
return onApplyWindowInsets(insets);
}
} finally {
mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
}
}
肯定是在这几个return中对insets重新做了处理并返回了一个新的insets
目前是在DecorView的子view中他是一个LinearLayout
他有一个特殊的标志:FITS_SYSTEM_WINDOWS
带着这些知识点进入了onApplyWindowInsets中
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
return onApplyFrameworkOptionalFitSystemWindows(insets);
}
......
}
由于这个LinearLayout的FITS_SYSTEM_WINDOWS的特性会进入到onApplyFrameworkOptionalFitSystemWindows这个方法
private WindowInsets onApplyFrameworkOptionalFitSystemWindows(WindowInsets insets) {
Rect localInsets = sThreadLocal.get();
WindowInsets result = computeSystemWindowInsets(insets, localInsets);
applyInsets(localInsets);
return result;
}
通过computeSystemWindowInsets生成了一个新的insets并作为了返回值
正是computeSystemWindowInsets(这里就不跟下去了)消耗掉了当前的insets,同时根据insets里面的所有insets的left、bottom、top、right分别找出这四个值的最大值给localInsets的四条边。
applyInsets(localInsets)的作用就是把该矩形的四条边作为该view的四周的初始padding值
measure的过程就会减去该view的padding值
那么问题来了,如果我们在自己的layout中指定setFitsSystemWindows会不会重复减去这里的padding呢?
实际验证后是不会的,为什么呢?
因为我们前面说了,在dispatchApplyWindowInsets中还没有传递到我们自己的layout中,该insets就被消耗了,就不会往下传递了,也就不会对我们的layout产生影响了。
这也回答了为什么我们的onApplyWindowInsets没有被调用的原因。
private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
insets = getChildAt(i).dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {//此处验证是否被消耗
break;
}
}
return insets;
}
结尾
既然onApplyWindowInsets可以使insets影响到我们当前view的padding。
我们是不是可以这么使用呢?
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
{
ViewCompat.setWindowInsetsAnimationCallback(this.getRootView(), new WindowInsetsAnimationCompat.Callback(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
insets.getInsets(WindowInsetsCompat.Type.ime());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Insets in = insets.getInsets(WindowInsetsCompat.Type.ime());
in = Insets.of(0,0,0,in.bottom-insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom);
WindowInsets w = new WindowInsets.Builder().setInsets(WindowInsets.Type.systemBars(),in.toPlatformInsets()).build();
onApplyWindowInsets(w);
}
return insets;
}
});
if(!ViewCompat.getRootWindowInsets(this).isVisible(WindowInsetsCompat.Type.ime()))
{
controller.show(WindowInsetsCompat.Type.ime());
}
else
{
controller.hide(WindowInsetsCompat.Type.ime());
}
break;
}
}
}
效果还算可以!