如何根据动态HTML自动调整Xamarin.Android(或Android)中WebView的高度?

How to auto-adjust the height of a WebView in Xamarin.Android (or Android) based on dynamic HTML?

我正在创建一个从 API 获取动态 HTML 网站的应用程序。我的主要挑战是我需要实时 自动调整 WebViewheight 因为 属性 android:layout_height="wrap_content"未按预期工作且未自动调整,其 height 始终为 0dp,并且从未显示。

我消费的API可以return这样简单的文字:

<p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p>

或包含图像、表格、次要格式等的复杂 HTML

在我这边,我尝试将WebView高度设置为wrap_content(如前所述)作为解决方法,同时,我在加载前隐藏它,重新加载完成后它,并在内容完全加载后再次显示,但没有任何工作正常。 height 要么是 0dp 要么是像 32dp 这样的小数字,几乎没有意义且不可见,因为几乎每个 HTML 都是长的。

这是我当前的代码,其中包含一些要测试的潜在示例:

XML:

<?xml version="1.0" encoding="UTF-8" ?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">
    <LinearLayout
        android:id="@+id/llOverview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/min_val"
        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/indeterminateBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:indeterminate="true"
            android:max="100"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_marginTop="@dimen/min_half_val"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="@dimen/text_size"
            android:id="@+id/lblDescription" />
        <android.webkit.WebView
            android:id="@+id/webViewExtra"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</ScrollView>

C#:

var lblDescription = FindViewById<TextView>(Resource.Id.lblDescription);

lblDescription.Text = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Potenti nullam ac tortor vitae purus faucibus ornare. Tellus elementum sagittis vitae et leo duis. Condimentum mattis pellentesque id nibh tortor id aliquet lectus proin. Elementum curabitur vitae nunc sed velit dignissim sodales ut. Vitae aliquet nec ullamcorper sit amet risus nullam eget. Quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Quam adipiscing vitae proin sagittis. Bibendum at varius vel pharetra vel turpis nunc. Bibendum at varius vel pharetra vel. Id diam vel quam elementum. Magna etiam tempor orci eu lobortis elementum nibh tellus. Ligula ullamcorper malesuada proin libero nunc. Gravida quis blandit turpis cursus. Ut pharetra sit amet aliquam id diam maecenas. Nisl rhoncus mattis rhoncus urna neque. Tempus egestas sed sed risus pretium quam vulputate dignissim. Et netus et malesuada fames ac. Malesuada fames ac turpis egestas maecenas pharetra convallis.";

var webView = FindViewById<Android.Webkit.WebView>(Resource.Id.webViewExtra);

webView.Visibility = ViewStates.Gone;
webView.Settings.VerticalScrollBarEnabled = true;
webView.Settings.JavaScriptEnabled = true;

var html = "<p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p><p>Lorem ipsum dolor sit amet consectetur adipiscing elit, malesuada per consequat conubia accumsan vulputate tincidunt, porttitor cum cras pretium diam inceptos. Augue fringilla id laoreet metus quisque eu accumsan ultrices fusce, vel hendrerit phasellus mollis arcu consequat risus suscipit, nostra orci lobortis at quis sed integer cubilia. Quisque turpis congue euismod class tristique magna at eros aenean quam, cum facilisis malesuada per auctor cubilia leo ultrices pharetra, praesent habitant ut nec feugiat velit pulvinar libero tellus.</p>";

if (!string.IsNullOrEmpty(html))
{

    html = $@"<!DOCTYPE html>
            <html>
                <head>
                    <meta charset='utf-8' />
                    <meta name='viewport' content='width=device-width, initial-scale=1'>
                </head>
                <body style='margin: 0px; background: #fafafa'>{html}</body>
            </html>";

    webView.SetWebViewClient(new JSOverviewHelper());

    webView.LoadData(html, "text/html", "UTF-8");

    webView.Reload();
}

public class JSOverviewHelper : WebViewClient
{
    public override void OnPageFinished(WebView webView, string url)
    {
        base.OnPageFinished(webView, url);

        webView.Visibility = Android.Views.ViewStates.Visible;
    }
}

除此之外,我已经尝试使用 JS 函数计算 height,但没有按预期工作。 returned height 往往是 WebView + 1px 的当前 height。但是,如果我 运行 使用 chrome://inspect 相同的函数,那么它 return 编辑了正确的奇数值。

这是使用 WebView 中的 OnPageFinished 事件测试此实验的函数:

public override void OnPageFinished(WebView webView, string url)
{
    base.OnPageFinished(webView, url);

    webView.Visibility = ViewStates.Visible;

    Thread.Sleep(1000);

    webView.EvaluateJavascript(@"(function() {
                                    var body = document.body,
                                        html = document.documentElement;

                                    return Math.max(body.scrollHeight, body.offsetHeight, 
                                                            html.clientHeight, html.scrollHeight, html.offsetHeight);
                                        })();", new WebViewValueCallback(webView));
}

public class WebViewValueCallback : Object, IValueCallback
{
    private readonly WebView webView;

    public WebViewValueCallback(WebView webView)
    {
        this.webView = webView;
    }

    public void OnReceiveValue(Object value)
    {
        var result = Convert.ToString(value);
        ViewGroup.LayoutParams vc = webView.LayoutParameters;
        vc.Height = int.Parse(Convert.ToString(value));
        webView.LayoutParameters = vc;
    }
}

此外,我尝试使用 OnPageCommitVisible(它是在 OnPageFinished 之前触发的)和 OnProgressChanged 使用基于 WebChromeClient 的自定义 class 作为建议here,当它达到 100% 时,JS 函数仍然 returned 一个与预期不同的值,它总是 returned 0 或 WebView + 1px 的当前高度和以前一样。

我还尝试了什么?我在 WebView 中设置了一个预定义的高度来测试应用程序,创建了一个静态页面,并使用 JS 加载了数据,但是 returned 高度仍然不正确。

知道加载后如何将高度调整到最新高度吗?谢谢

P.S.:

经过几次尝试,我能够为我的案例创建一个“可行”的解决方案:

  1. 修改您的 WebView 并用 LinearLayout 包围它,配置如下:
<LinearLayout
    android:id="@+id/llWebViewExtras"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:orientation="vertical">
    <android.webkit.WebView
        android:id="@+id/webViewExtra"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
  1. 在您的 C# 部分添加此配置并使用您的 LinearLayout.
  2. 创建一个静态对象
public static LinearLayout LlWebViewExtras { get; set; }
private WebView webView;

protected override void OnCreate(Bundle savedInstanceState)
{
    LlWebViewExtras = FindViewById<LinearLayout>(Resource.Id.llWebViewExtras);
    webView = FindViewById<WebView>(Resource.Id.webViewExtra);

    webView.Settings.JavaScriptEnabled = true;
    webView.VerticalScrollBarEnabled = true;
    string script = $"javascript:myFunction();";
    webView.AddJavascriptInterface(new CallJSInterface(), "CSharp");
    webView.SetWebViewClient(new JSHelper(script));
    webView.LoadUrl("file:///android_asset/website/mywebsite.html");
}

public class JSHelper : WebViewClient
{
    private readonly string script;

    public JSHelper(string script)
    {
        this.script = script;
    }

    public override void OnPageFinished(WebView webView, string url)
    {
        base.OnPageFinished(webView, url);

        webView.Visibility = ViewStates.Visible;

        webView.EvaluateJavascript(script, null);
    }
}

  1. 创建一个捕获高度的函数,修改LinearLayout高度,在主线程中修改为dps
public class CallJSInterface : Java.Lang.Object
{
    [Export]
    [JavascriptInterface]
    public void CurrentHeight(int height)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            var llWebViewExtras = MyActivity.LlWebViewExtras;

            using ViewGroup.LayoutParams vc = llWebViewExtras.LayoutParameters;
            vc.Height = ConvertPixelsToDp(height);
            llWebViewExtras.LayoutParameters = vc;
        });
    }

    public static int ConvertPixelsToDp(float px)
    {
        return (int) (px * Resources.System.DisplayMetrics.Density);
    }
}
  1. 在你的 JS 中创建一个函数,returns 以 px 为单位的最大高度:
function myFunction() {
    CSharp.CurrentHeight(latestHeight());
}

function getLatestHeight() {
    let cHeight = getHeight();

    if (cHeight === 0) {
        //content is the element that contains the data. I used a Div
        let content = document.getElementById("content");
        let contentTmp = document.getElementById("content");
        contentTmp = contentTmp.cloneNode(true);
        contentTmp.id = "tmpcontent";
        content.style.visibility = "hidden";
        contentTmp.offsetHeight + 0;
        document.body.appendChild(contentTmp);
        document.body.removeChild(document.getElementById("content"));
        content.style.visibility = "visible";

        return getHeight();
    }
    
    return cHeight;
}

function getHeight() {
    let body = document.body,
        html = document.documentElement;

    return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight, body.getBoundingClientRect().height);
}

我提出的这个方案很好,但不是万能的。它倾向于从时间和 returns 0 开始失败,因为它的 height。在这些情况下,我什至配置为重新加载 WebView 并每隔几毫秒检查几次其高度,但没有任何成功结果。

我根据这个答案添加了一些额外的验证:

它的高度增加了一倍,但至少它不会 return 高度为 0。

另外,我为这些情况创建了这个额外的解决方法:

webView.SetOnTouchListener(new OnTouchListener(webView));

public class OnTouchListener : Java.Lang.Object, View.IOnTouchListener
{
    private readonly int maxTop;
    private readonly int maxBottom;
    private readonly WebView webView;

    private float downX, downY;
    private int totalY;
    private int scrollByX, scrollByY;

    public OnTouchListener(WebView webView)
    {
        var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
        // set scroll limits
        maxTop = 0;
        maxBottom = (int)mainDisplayInfo.Height;

        this.webView = webView;
    }

    public bool OnTouch(View v, MotionEvent e)
    {
        float currentX, currentY;

        if (e.Action == MotionEventActions.Down)
        {
            downX = e.GetX();
            downY = e.GetY();
        }
        else if (e.Action == MotionEventActions.Move)
        {
            currentX = e.GetX();
            currentY = e.GetY();
            scrollByX = (int)(downX - currentX);
            scrollByY = (int)(downY - currentY);

            // scrolling to top of image (pic moving to the bottom)
            if (currentY > downY)
            {
                if (totalY == maxTop)
                {
                    scrollByY = 0;
                }
                if (totalY > maxTop)
                {
                    totalY += scrollByY;
                }
                if (totalY < maxTop)
                {
                    scrollByY = maxTop - (totalY - scrollByY);
                    totalY = maxTop;
                }
            }

            // scrolling to bottom of image (pic moving to the top)
            if (currentY < downY)
            {
                if (totalY == maxBottom)
                {
                    scrollByY = 0;
                }
                if (totalY < maxBottom)
                {
                    totalY += scrollByY;
                }
                if (totalY > maxBottom)
                {
                    scrollByY = maxBottom - (totalY - scrollByY);
                    totalY = maxBottom;
                }
            }

            webView.ScrollBy(scrollByX, scrollByY);
            downX = currentX;
            downY = currentY;
        }

        return true;
    }
}

但是,如果您知道如何修复它并达到真实高度,我将非常高兴。

P.S.:

我什至在 Chromium 项目中开了一个工单,他们接受了最后一部分作为一个错误:

https://bugs.chromium.org/p/chromium/issues/detail?id=1232745