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

mlmc2os5  于 2023-03-28  发布在  Android
关注(0)|答案(1)|浏览(114)

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

<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 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,但没有像预期的那样工作。返回的height往往是WebView + 1px的当前height。然而,如果我使用chrome://inspect运行相同的函数,那么它返回的正确值是奇数。
这是使用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的自定义类作为建议here,当它达到100%时,JS函数仍然返回与预期不同的值,它总是返回0或WebView + 1px的当前高度。
我还尝试了什么?我在WebView中设置了一个预定义的高度来测试应用程序,创建了一个静态页面,并使用JS加载了数据,但仍然,返回的高度不正确。
请问装完后我怎么把高度调整到最新的?谢谢。

附:

  • 如果你知道如何在Java或Kotlin中修复它,我很可能会知道如何将其转换为C#代码。从我的Angular 来看,这仍然是一个有效的答案。
  • TextView是强制性的,因为它在网站之前显示了一个摘录,而网站可以是可选的。因此,它不能被删除。
1l5u6lss

1l5u6lss1#

经过几次尝试,我能够为我的案例创建一个“可行”的解决方案:
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创建一个静态对象。

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中创建一个函数,返回以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);
}

我提出的这个解决方案很好,但它不是防弹的。它往往会随着时间的推移而失败,并返回0作为其height。我甚至配置为在这些情况下重新加载WebView,并每隔几毫秒检查几次其高度,但没有任何成功的结果。
我在这个答案的基础上添加了一些额外的验证:https://stackoverflow.com/a/27729544/1928691
它使高度加倍,但至少它不会返回高度为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;
    }
}

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

附:

我甚至在Chromium项目中开了一张票,他们接受了最后一部分作为bug:
https://bugs.chromium.org/p/chromium/issues/detail?id=1232745

相关问题