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;
}
}
我有一个 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;
}
}