Android Insets 软键盘防遮挡

400 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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;
            }
      }
  }

效果还算可以!

微信截图_20221215082551.png