Xamarin.Forms ListView 内的自定义 WebView,每行计算高度

Xamarin.Forms Custom WebViews inside a ListView with calculated height per row

我有一个 ListView,我想在每一行中放置一个自定义 HTMLWebView。由于这些 WebView 的高度因插入的内容而异,因此我希望计算高度并将其应用于每个单独的 WebView。

如果我将 WebView 放在网格中,它就可以工作。但是,如果我将 WebViews 放在 ListView 中,则只计算最后一行的高度,我不明白为什么。

所有代码都可以在这里找到: https://github.com/SlimboTimbo/MultiWebviews

<ListView x:Name="lstView" HasUnevenRows="True" VerticalOptions="FillAndExpand">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <custom:HybridWebView Grid.Row="0" messageContent="{ Binding.Name}" />
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

</ListView>

如果你想在运行时改变ViewCell的高度。您应该调用 cell.ForceUpdateSize(); 来更新单元格的大小。

在自定义渲染器中

 if (_xwebView != null)
{
    view.Settings.JavaScriptEnabled = true;
    await Task.Delay(100);
    string result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");

    _xwebView.HeightRequest = Convert.ToDouble(result);

    MessagingCenter.Send<Object, double>(this, "LoadFinished", Convert.ToDouble(result));

}

在 HybridWebView 中

   public HybridWebView()
    {

        Source = "";
        this.PropertyChanged += HybridWebView_PropertyChanged;

        MessagingCenter.Subscribe<Object, double>(this, "LoadFinished", (args, height) =>
        {


            var parent = this.Parent;
            while (parent != null && !(parent is ViewCell))
            {
                parent = parent.Parent;
            }
            if (parent is ViewCell cell)
            {
                Device.BeginInvokeOnMainThread(() =>
                {
                    this.HeightRequest = height;
                    cell.ForceUpdateSize();
                });
            }

        });

    }

更新

在自定义渲染器中

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using WebView = Android.Webkit.WebView;

using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

using MultiWebviews.Controls;
using MultiWebviews.Droid.Renderers;
using Android.Webkit;
using System.Threading.Tasks;
using System.ComponentModel;

[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace MultiWebviews.Droid.Renderers
{
    public class HybridWebViewRenderer : WebViewRenderer
    {

        public static int _webViewHeight;
        HybridWebView _xwebView = null;
        WebView _webView;
        Context _context;
        public HybridWebViewRenderer(Context context) : base(context)
        {
            _context = context;
        }



        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (Control != null)
            {
                Control.Settings.JavaScriptEnabled = true;
            }



            base.OnElementPropertyChanged(sender, e);
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            _xwebView = e.NewElement as HybridWebView;
            _webView = Control;

            Control.SetWebViewClient(new HybridWebViewClient(_xwebView));

        }

    }



    public class HybridWebViewClient : WebViewClient
    {
        WebView _webView;
        HybridWebView _xwebView = null;


        public HybridWebViewClient(HybridWebView webview)
        {
            _xwebView = webview;
        }

        public async override void OnPageFinished(WebView view, string url)
        {
            try
            {
                _webView = view;
                if (_xwebView != null)
                {
                    view.Settings.JavaScriptEnabled = true;
                    await Task.Delay(100);
                    string result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");

                    _xwebView.HeightRequest = Convert.ToDouble(result);




                    MessagingCenter.Send<Object, PassModel>(this, "LoadFinished", new PassModel(_xwebView.Id, Convert.ToDouble(result)));

                }
                base.OnPageFinished(view, url);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message}");
            }
        }
    }
}

在 HybridWebView 中

添加可绑定的 属性 Id

using System;


using Xamarin.Forms;

namespace MultiWebviews.Controls
{


    public class HybridWebView : WebView
    {

        bool LoadFinish;

        public static readonly BindableProperty IdProperty =
          BindableProperty.Create(nameof(Id), typeof(string), typeof(HybridWebView), string.Empty);

        public string Id
        {
            get { return (string)GetValue(IdProperty).ToString(); }
            set { 
                SetValue(IdProperty, value); 
            }
        }

        Action<string> action;

        public static readonly BindableProperty messageContentProperty =
          BindableProperty.Create(nameof(messageContent), typeof(string), typeof(HybridWebView), string.Empty);

        public string messageContent
        {
            get { return (string)GetValue(messageContentProperty).ToString(); }
            set { 
                SetValue(messageContentProperty, value); 
            }
        }

        public void Cleanup()
        {
            action = null;
        }

        public void InvokeAction(string data)
        {
            if (action == null || data == null)
            {
                return;
            }
            action.Invoke(data);
        }


        public HybridWebView()
        {

            Source = "";
            this.PropertyChanged += HybridWebView_PropertyChanged;

            MessagingCenter.Subscribe<Object, PassModel>(this, "LoadFinished", (args, obj) =>
            {
                var id = obj.Id;

                var model = obj as PassModel;

                if(model.Id ==this.Id&&!LoadFinish)
                {
                    LoadFinish = true;
                    var parent = this.Parent;
                    while (parent != null && !(parent is ViewCell))
                    {
                        parent = parent.Parent;
                    }
                    if (parent is ViewCell cell)
                    {
                        Device.BeginInvokeOnMainThread(() =>
                        {
                            this.HeightRequest = model.Height;
                            cell.ForceUpdateSize();
                        });
                    }
                }



            });

        }

        private void HybridWebView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {

            if (e.PropertyName.Equals(nameof(messageContent)))
            {
                var htmlSource = new HtmlWebViewSource();
                htmlSource.Html = @"<html>
                                    <head>
                                    <link rel=""stylesheet"" href=""chatmessage-content.css"">
                                    <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no'>
                                    </head>
                                    <body style=""background-color: #DDDDDD;"">
                                        <a class=""btn btn-true"" onclick=""setresponseandsubmit('1ca15124-9624-431e-8644-22a5839b7a10')"">test: </a>"
                                        + messageContent + 
                                        "<p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p><p>Extra line</p>" +
                                    "</body></html>";
                Source = htmlSource;




            }
        }
    }
}

在xaml

<custom:HybridWebView Id="{Binding Id}" Grid.Row="1" messageContent="{ Binding Name}" />

在代码后面

确保每个 ID 都不同。

pages = new ObservableCollection<PageViewModel>();
pages.Add(new PageViewModel{ Id = "0",  Name ="Test 1" });
pages.Add(new PageViewModel{ Id ="1" ,Name = "Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 " });
pages.Add(new PageViewModel { Id = "2", Name ="Test 3" });
pages.Add(new PageViewModel { Id = "3" ,Name = "Test 4 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 Test 2 " });

lstView.ItemsSource = pages;

并在 Forms 项目中添加一个新的 class 以在 MessageingCenter 中传递值。

public class PassModel
    {
        public string Id { get; private set; }
        public double  Height { get; private set; }

        public  PassModel(string id, double height)
        {
            Id = id;
            Height = height;
        }

    }