Android-WebView软键盘遮挡输入框优化纪实

2,852 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 7 天,点击查看活动详情


背景

笔者在使用 WebView 加载含有输入框的 H5 页面时,点击输入框后,输入框会被软键盘遮挡住,无法看到输入的内容,这很影响用户体验。

笔者想着这种业务场景比较常见,遂上网搜索一番,果不其然,有不少同志遇到这个问题,想来这个问题很好解决了。笔者一一尝试了同志们提供的解决方案,结果要不是没有作用,要不是效果不太满意,只好自己另辟蹊径了。

注:在笔者的业务场景中,App是全屏的,即没有顶部的系统栏,也没有底部的导航栏,所以笔者的解决方案,可能不适用于其他场景。

纪实

方案

好的,重新梳理下遇到的问题,目前的问题是:用户无法看到输入框里的内容,那么我们可以先让用户看到输入框里的内容。

笔者想到了第一种方案:**输入框被软键盘遮挡后,在软键盘输入时,可以在 H5 页面顶部实时显示输入框里的内容。**本方案解决了输入框被软键盘遮挡后看不到输入内容的问题,但是没有解决输入框被软键盘遮挡的问题,此方案一定程度上提升了用户体验,不过笔者认为本方案不是很完美,继续思考其他方案。

最后笔者想到了第二种方案,大体思路如下:

  1. 笔者的业务场景,App是全屏的,所以整个 WebView 也是全屏的,
  2. 在点击 H5 页面的输入框时,H5 获取当前输入框左下角的 Y 坐标,然后把左下角的 Y 坐标通知到 Android 原生,
  3. Android 原生获取软键盘顶部的 Y 坐标与输入框左下角的 Y 坐标进行比较,如果小于左下角的 Y 坐标,则认为此时输入框被软键盘遮挡,需要将 WebView 向上滚动相应的距离以显示出来输入框,否则判断 WebView 当前的滚动距离是否为 0,如果不为 0 则取当前距离的负值作为滚动距离,否则就不滚动。

上述方案的文字描述可能比较绕,我们可以看下面的流程图跟上思路:

image-20220605214012705

实现

接下来笔者根据上面的方案二进行代码上的具体实现:

首先是获取输入框左下角的 Y 坐标,这一步如果让前端来实现的话,是比较容易的,不过笔者认为方案二是一种比较通用的解决方案,可以提取出来作为二方库来使用,如果让其他使用二方库项目组的前端同志也来实现一次的话就比较麻烦了,所以在这一步,笔者选择了在 Android 端注入 JS 的方案:

ready();

document.addEventListener("readystatechange", function () {
    var readyState = document.readyState;
    if (readyState !== "loading") {
        console.log(readyState);
        ready();
    }
});

document.addEventListener("DOMContentLoaded", function () {
    console.log("DOMContentLoaded");

    ready();
});

function ready() {
    document.body.addEventListener("DOMNodeInserted", function () {
        webInput();
    });

    webInput();
}

function webInput() {
    var input = document.querySelectorAll("input") || [];
    console.log("input -> " + input.length);

    if (input.length === 0) {
        return;
    }

    input.forEach(function (value) {

        var type = value.getAttribute("type");
        console.log("type -> " + type);

        if (type === null
            || type === "number"
            || type === "search"
            || type === "password"
            || type === "tel"
            || type === "email"
            || type === "url"
            || type === "text") {

            value.removeEventListener("click", webInput2Android);

            value.removeEventListener("focus", webInput2Android);

            value.addEventListener("click", webInput2Android);

            value.addEventListener("focus", webInput2Android);
        }
    });
}

function webInput2Android() {
    console.log("webInput2Android");

    var offset = getOffset(this);
    var x = offset.x;
    var y = offset.y;
    console.log("x:y --> " + x + ":" + y);

    var width = this.offsetWidth;
    var height = this.offsetHeight;
    console.log("w:h --> " + width + ":" + height);

	// Send to Android
}

function getOffset (el) {
    var box = el.getBoundingClientRect();
    return {
        x: box.left + window.pageXOffset - document.documentElement.clientLeft,
        y: box.top + window.pageYOffset - document.documentElement.clientTop
    }
}

在 JS 中通过监听输入框的点击事件,在点击时获取输入框左上角的坐标和输入框的宽高,间接算出输入框左下角的坐标,然后通知到 Android 原生:

public void run() {
    Activity activity = mActivityReference.get();
    WebView webView = mWebViewReference.get();
    if (activity == null || webView == null) {
        return;
    }

    // JS 传入的输入框左下角 Y 坐标
    int bottom = mWebInput.getBottom();
    
    // 获取软键盘顶部的 Y 坐标
    int keyboardTop = KeyboardHelper.getKeyboardTop(activity);
    
    // WebView 当前滚动的距离
    int scrollY = webView.getScrollY();
    
    // 判断软键盘是否弹出
    if (keyboardTop != -1) {
        // 判断输入框是否被软键盘遮挡
        if (bottom >= keyboardTop) {
            // 计算滚动距离
            int diff = bottom - keyboardTop - scrollY;
            // 滚动 WebView diff + mDistance 距离,mDistance 默认 50,
            // diff + mDistance:即软键盘距离输入框底部 50 距离 
            webView.assistWebKeyboard(scrollY, diff + mDistance);
        } else {
            // 判断 WebView 是否有滚动
            if (scrollY != 0) {
                // 滚动 WebView
                webView.assistWebKeyboard(scrollY, -scrollY);
            }
        }
    }
}

以上代码实现方案二中第三步,首先获取 JS 传入的输入框左下角 Y 坐标,其次获取软键盘顶部的 Y 坐标,然后获取 WebView 当前滚动的距离,接下来根据软键盘顶部的 Y 坐标判断软键盘是否弹出,在软键盘弹出的情况下,判断输入框左下角 Y 坐标是否大于等于软键盘顶部 Y 坐标:

  1. 如果大于等于,则认为输入框被软键盘遮挡,此时计算出 WebView 需要滚动多长的距离,输入框才能被显示出来,最后调用 assistWebKeyboard 方法滚动 WebView
  2. 如果小于,则认为输入框没有被软键盘遮挡,此时判定 WebView 之前是否有滚动,如果有滚动距离,则计算滚动距离为当前滚动距离的负值,最后调用 assistWebKeyboard 方法滚动 WebView

至此完结。

总结

本文提供了一种新的解决 WebView 输入框被软键盘遮挡的思路,不过这种思路也有它的局限性,目前来看仅适用于全屏的 WebView 中,后续笔者再想到其他方案时,另写一篇纪实续集吧。

期待与各位同志交流方案与思路😀

happy,希望可以帮到你~