WebView输入框软键盘遮挡问题(沉浸状态栏和adjustResize的冲突)

614 阅读2分钟

【WebView为什么没有在软键盘弹出时更新布局】

默认Activity情况下,软键盘弹出时,通过给DecorView的LinearLayout添加"layout_margin_bottom=键盘高度",实现R.id.content避开软键盘。

2022-11-12-10-30-45.png

image.png

如果你的WebView出现输入框被软键盘遮挡问题,需要查看下为什么上述逻辑没有生效?

我在项目上遇到的原因是,WebView页面设置了window布局延伸到状态栏,导致 adjustResize 无法生效,窗口尺寸也就不会适配软键盘。

// 布局延伸到状态栏
int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
mWindow.getDecorView().setSystemUiVisibility(uiFlags);

【如何手动处理软键盘更新布局】

通用解决方案是 addOnGlobalLayoutListener 来监听,网上有现成的方案叫 AndroidBug5497Workaround

我采用了这个方案,差点导致了线上Live Issue。原因是这个方案的适用场景是全屏显示,既沉浸到状态栏,也沉浸到了底部虚拟按键高度,导致WebView被底部虚拟按键吃进去一部分,最下面的内容不可见!!!每一个方案都有它适用的场景,不理解清楚为什么要这么写,不要随便拿来使用哈。

这里需要对DecorView的布局有一个详细的了解

DecorView 中添加的第一个View是 mContentRoot,mContentRoot 比较常见的布局文件是 R.layout.screen_simple。PhoneWindow 的 mContentParent 是 DecorView 布局中对应 ID 为“content”的视图。除了视图“content”之外,DecorView 的视图还有 ID 为“title” 的 WindowTitle。

setContentView 时,创建 subDecor。subDecor 布局通常是 R.layout.abc_screen_simple,它的 R.id.action_bar_activity_content 会被重命名为 android.R.id.content(原来DecorView中的android.R.id.content被改名了),subDecor 创建后被 addView 到 PhoneWindow#mContentParent。contentView的内容,又会addView到 R.id.action_bar_activity_content。(AppCompatDelegateImpl#createSubDecor())。

状态栏和导航栏是DecorView中额外addView的两个子View(updateColorViewInt()

DecorView的三个子View:

  1. LinearLayout,上顶到窗口(paddingTop让开状态栏),下挨着导航栏(layout_marginBottom)
  2. View 状态栏,上
  3. View 导航栏,下
DecorView树状结构

DecorView布局

ImmersionBar 开源库的实现可以拿来参考,精简版实现如下:

class WebSoftInputHelper {

    // For more information, see https://issuetracker.google.com/issues/36911528

    public static void assistActivity(@NonNull Activity activity) {
        new WebSoftInputHelper(activity);
    }

    private final View mContentParent;
    View mDecorView;
    private int keyboardHeightPrevious;

    private WebSoftInputHelper(@NonNull Activity activity) {
        mDecorView = activity.getWindow().getDecorView();
        mContentParent = mDecorView.findViewById(android.R.id.content);
        mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                possiblyResizeChildOfContent();
            }
        });
    }

    private void possiblyResizeChildOfContent() {
        Rect r = new Rect();
        mContentParent.getWindowVisibleDisplayFrame(r);
        int keyboardHeight = mContentParent.getBottom() - r.bottom;
        if (keyboardHeight != keyboardHeightPrevious) {
            mContentParent.setPadding(mContentParent.getPaddingLeft(),
                    mContentParent.getPaddingTop(),
                    mContentParent.getPaddingRight(),
                    keyboardHeight);
            keyboardHeightPrevious = keyboardHeight;
        }
    }
}

解释下 getWindowVisibleDisplayFrame 方法:

  1. 不管通过哪个View调用,最终会调用到所在Window的可视化区域
  2. 这里暂时不计算状态栏对top的影响
  3. 软键盘弹出的时候,bottom = 屏幕高度 - 软键盘 - 虚拟按键,mContentView.getHeight() - rect.bottom = 软键盘
  4. 软键盘关闭时,bottom = 屏幕高度 - 虚拟按键,mContentView.getHeight() - rect.bottom = 0