Skip to content

WebView在NestedScrollView中高度不稳定

WebView在NestedScrollView中高度不稳定,首次加载后,webView底部会有一大块空白区域。

本文中使用的是com.tencent.smtt.sdk.WebView

layout层级如下。NestedScrollView包着一个LinearLayout,然后再包着WebView

<androidx.core.widget.NestedScrollView
    android:id="@+id/fisher_nest"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <!-- 其他view -->
        <com.tencent.smtt.sdk.WebView
            android:id="@+id/fisher_web"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/transparent" />

        <!-- 其他view -->
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

加载的是html文本数据,使用的加载方式如下

wv.loadDataWithBaseURL(null, "<html>html文本数据</html>", "text/html", "UTF-8", null);

首次加载后会发现webView底部有一大块空白。假设文本需要的高度是1000px,但实际展示高度是4000px。 而替换html内容再次加载后,webView高度会变矮一些。

为了规避这个问题,我们采用加载空文本 + 动态测量高度的方式。

预加载空文本

wv是WebView。 在加载目标文本之前,先加载一个中间信息。然后延时显示目标文本。

// 先显示点其他的信息
wv.loadDataWithBaseURL(null, "<html><body><div id='whole'>welcome to an.rustfisher.com</div></body></html>", 
    "text/html", "UTF-8", null);
mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        // 显示目标文本
    }
}, 700);

js接口

html文本中,给内容最外层包上一个<div>,并打上标示。

例如

<html>
    <body>
        <div id='whole'>这里是我们的目标信息 - an.rustfisher.com</div>
    </body>
</html>

新建监听器JSActionListener。定义方法onResize(float height)

public abstract class JSActionListener {
    public void onResize(float height) {

    }
}

新建JSAction类,里面增加js接口方法,需要使用@JavascriptInterface注解。

public class JSAction {
    public static final String JS_INTERFACE_NAME = "android"; // 提供给js
    private JSActionListener listener;
    private Handler mHandler = new Handler(Looper.getMainLooper());

    public JSAction(JSActionListener listener) {
        this.listener = listener;
    }

    @JavascriptInterface
    public void resize(final float height) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                listener.onResize(height);
            }
        });
    }
}

js接口名称是resize,后面要用js来调用它。

配置WebView

分为2步。首先把前面定义好的接口用起来。js要和Java进行沟通。

webView需要允许js方法。setJavaScriptEnabled已经deprecated,但这里为了方便继续使用。

添加js接口

WebSettings webSettings = wv.getSettings();
webSettings.setJavaScriptEnabled(true);
wv.addJavascriptInterface(new JSAction(new JSActionListener() {
    @Override
    public void onResize(final float height) {
        super.onResize(height);
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) wv.getLayoutParams();
        lp.height = (int) (height * getResources().getDisplayMetrics().density);
        wv.setLayoutParams(lp);
    }
}), JSAction.JS_INTERFACE_NAME);

页面加载完成

监听到页面加载完毕后,让webView去调用js方法。

  • android就是前面定义的JSAction.JS_INTERFACE_NAME
  • document.getElementById(\"whole\").offsetHeight 能拿到id为whole的元素的高度。
    • 这里不定义id应该也可以,直接用document.body.getBoundingClientRect()应该也行。
wv.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        wv.loadUrl("javascript:android.resize(document.getElementById(\"whole\").offsetHeight)");
        super.onPageFinished(view, url);
    }
});

运行表现是,先显示welcome to an.rustfisher.com字样,然后再显示目标文本数据。

更进一步的做法是,在onPageFinished监听到中间数据显示完毕后,再去加载目标数据。