Xamarin.Forms自定义用户界面控件实现一个HybridWebView(混合webview)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Xamarin.Forms自定义用户界面控件实现一个HybridWebView(混合webview)相关的知识,希望对你有一定的参考价值。
呈现一个特定于平台的视图
1 public class HybridWebView : View 2 { 3 Action<string> action; 4 public static readonly BindableProperty UriProperty = BindableProperty.Create ( 5 propertyName: "Uri", 6 returnType: typeof(string), 7 declaringType: typeof(HybridWebView), 8 defaultValue: default(string)); 9 10 public string Uri { 11 get { return (string)GetValue (UriProperty); } 12 set { SetValue (UriProperty, value); } 13 } 14 15 public void RegisterAction (Action<string> callback) 16 { 17 action = callback; 18 } 19 20 public void Cleanup () 21 { 22 action = null; 23 } 24 25 public void InvokeAction (string data) 26 { 27 if (action == null || data == null) { 28 return; 29 } 30 action.Invoke (data); 31 } 32 }
HybridWebView自定义控件是在可移植类库(PCL)项目中创建的,并为控件定义了以下API:
1 <ContentPage ... 2 xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer" 3 x:Class="CustomRenderer.HybridWebViewPage" 4 Padding="0,20,0,0"> 5 <ContentPage.Content> 6 <local:HybridWebView x:Name="hybridWebView" Uri="index.html" 7 HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" /> 8 </ContentPage.Content> 9 </ContentPage>
本地命名空间前缀可以命名为任何东西。但是,clr命名空间和程序集值必须与自定义控件的细节相匹配。声明命名空间后,将使用前缀引用自定义控件。
1 public class HybridWebViewPageCS : ContentPage 2 { 3 public HybridWebViewPageCS () 4 { 5 var hybridWebView = new HybridWebView { 6 Uri = "index.html", 7 HorizontalOptions = LayoutOptions.FillAndExpand, 8 VerticalOptions = LayoutOptions.FillAndExpand 9 }; 10 ... 11 Padding = new Thickness (0, 20, 0, 0); 12 Content = hybridWebView; 13 } 14 }
HybridWebView实例将用于在每个平台上显示本机web控件。它的Uri属性设置为一个HTML文件,该文件存储在每个平台特定的项目中,并由本机web控件显示。呈现的HTML要求用户输入他们的名字,JavaScript函数在响应HTML按钮时调用C#操作。
1 public partial class HybridWebViewPage : ContentPage 2 { 3 public HybridWebViewPage () 4 { 5 ... 6 hybridWebView.RegisterAction (data => DisplayAlert ("Alert", "Hello " + data, "OK")); 7 } 8 }
这个操作调用DisplayAlert方法来显示一个模式弹出,它显示了由HybridWebView实例显示的HTML页面中的名称。
1 protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e) 2 { 3 base.OnElementChanged (e); 4 5 if (Control == null) { 6 // Instantiate the native control and assign it to the Control property with 7 // the SetNativeControl method 8 } 9 10 if (e.OldElement != null) { 11 // Unsubscribe from event handlers and cleanup any resources 12 } 13 14 if (e.NewElement != null) { 15 // Configure the control and subscribe to event handlers 16 } 17 }
当控件属性为空时,应该只实例化一个新的本机控件。只有当自定义渲染器附加到新的Xamarin.Forms元素时,才应该配置该控件并订阅事件处理程序。类似地,任何订阅到的事件处理程序都应该只在呈现元素时取消订阅。采用这种方法将有助于创建一个不受内存泄漏影响的性能自定义渲染器。
1 <html> 2 <body> 3 <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> 4 <h1>HybridWebView Test</h1> 5 <br/> 6 Enter name: <input type="text" id="name"> 7 <br/> 8 <br/> 9 <button type="button" onclick="javascript:invokeCSCode($(‘#name‘).val());">Invoke C# Code</button> 10 <br/> 11 <p id="result">Result:</p> 12 <script type="text/javascript"> 13 function log(str) 14 { 15 $(‘#result‘).text($(‘#result‘).text() + " " + str); 16 } 17 18 function invokeCSCode(data) { 19 try { 20 log("Sending Data:" + data); 21 invokeCSharpAction(data); 22 } 23 catch (err){ 24 log(err); 25 } 26 } 27 </script> 28 </body> 29 </html>
这个Web页面允许用户在input元素中输入他们的名字,并提供一个button元素,当单击时将调用C#代码。实现这一目标的过程如下:
1 [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))] 2 namespace CustomRenderer.iOS 3 { 4 public class HybridWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>, IWKScriptMessageHandler 5 { 6 const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}"; 7 WKUserContentController userController; 8 9 protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e) 10 { 11 base.OnElementChanged (e); 12 13 if (Control == null) { 14 userController = new WKUserContentController (); 15 var script = new WKUserScript (new NSString (JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false); 16 userController.AddUserScript (script); 17 userController.AddScriptMessageHandler (this, "invokeAction"); 18 19 var config = new WKWebViewConfiguration { UserContentController = userController }; 20 var webView = new WKWebView (Frame, config); 21 SetNativeControl (webView); 22 } 23 if (e.OldElement != null) { 24 userController.RemoveAllUserScripts (); 25 userController.RemoveScriptMessageHandler ("invokeAction"); 26 var hybridWebView = e.OldElement as HybridWebView; 27 hybridWebView.Cleanup (); 28 } 29 if (e.NewElement != null) { 30 string fileName = Path.Combine (NSBundle.MainBundle.BundlePath, string.Format ("Content/{0}", Element.Uri)); 31 Control.LoadRequest (new NSUrlRequest (new NSUrl (fileName, false))); 32 } 33 } 34 35 public void DidReceiveScriptMessage (WKUserContentController userContentController, WKScriptMessage message) 36 { 37 Element.InvokeAction (message.Body.ToString ()); 38 } 39 } 40 }
HybridWebViewRenderer类将HybridWebView.Uri属性中指定的web页面加载到原生WKWebView控件中,并将invokeCSharpAction JavaScript函数注入到web页面中。一旦用户输入他们的名字并单击HTML按钮元素,执行invokeCSharpAction JavaScript函数,DidReceiveScriptMessage方法被称为接收到消息后从web页面。反过来,该方法调用杂交 HybridWebView.InvokeAction 方法,它将调用注册的操作以显示弹出窗口。
1 [assembly: ExportRenderer (typeof(HybridWebView), typeof(HybridWebViewRenderer))] 2 namespace CustomRenderer.Droid 3 { 4 public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Android.Webkit.WebView> 5 { 6 const string JavaScriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}"; 7 8 protected override void OnElementChanged (ElementChangedEventArgs<HybridWebView> e) 9 { 10 base.OnElementChanged (e); 11 12 if (Control == null) { 13 var webView = new Android.Webkit.WebView (Forms.Context); 14 webView.Settings.JavaScriptEnabled = true; 15 SetNativeControl (webView); 16 } 17 if (e.OldElement != null) { 18 Control.RemoveJavascriptInterface ("jsBridge"); 19 var hybridWebView = e.OldElement as HybridWebView; 20 hybridWebView.Cleanup (); 21 } 22 if (e.NewElement != null) { 23 Control.AddJavascriptInterface (new JSBridge (this), "jsBridge"); 24 Control.LoadUrl (string.Format ("file:///android_asset/Content/{0}", Element.Uri)); 25 InjectJS (JavaScriptFunction); 26 } 27 } 28 29 void InjectJS (string script) 30 { 31 if (Control != null) { 32 Control.LoadUrl (string.Format ("javascript: {0}", script)); 33 } 34 } 35 } 36 }
HybridWebViewRenderer类加载将HybridWebView.Uri属性中指定的web页面加载到一个原生WebView控件中,而invokec锐动作JavaScript函数被注入到web页面中,在web页面加载之后,使用了InjectJS方法。
1 public class JSBridge : Java.Lang.Object 2 { 3 readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer; 4 5 public JSBridge (HybridWebViewRenderer hybridRenderer) 6 { 7 hybridWebViewRenderer = new WeakReference <HybridWebViewRenderer> (hybridRenderer); 8 } 9 10 [JavascriptInterface] 11 [Export ("invokeAction")] 12 public void InvokeAction (string data) 13 { 14 HybridWebViewRenderer hybridRenderer; 15 16 if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget (out hybridRenderer)) { 17 hybridRenderer.Element.InvokeAction (data); 18 } 19 } 20 }
该类必须从 Java.Lang.Object 派生,而暴露于JavaScript的方法必须使用[JavascriptInterface]和[导出]属性来修饰。因此,当invokeCSharpAction JavaScript函数被注入到web页面并被执行时,它将调用由于[JavascriptInterface]和[Export(" invokeAction "])属性修饰的JSBridge.InvokeAction方法。反过来,InvokeAction方法调用混合HybridWebView.InvokeAction方法,它将调用注册的操作以显示弹出窗口。
1 [assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))] 2 namespace CustomRenderer.WinPhone81 3 { 4 public class HybridWebViewRenderer : ViewRenderer<HybridWebView, Windows.UI.Xaml.Controls.WebView> 5 { 6 const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}"; 7 8 protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e) 9 { 10 base.OnElementChanged(e); 11 12 if (Control == null) 13 { 14 SetNativeControl(new Windows.UI.Xaml.Controls.WebView()); 15 } 16 if (e.OldElement != null) 17 { 18 Control.NavigationCompleted -= OnWebViewNavigationCompleted; 19 Control.ScriptNotify -= OnWebViewScriptNotify; 20 } 21 if (e.NewElement != null) 22 { 23 Control.NavigationCompleted += OnWebViewNavigationCompleted; 24 Control.ScriptNotify += OnWebViewScriptNotify; 25 Control.Source = new Uri(string.Format("ms-appx-web:///Content//{0}", Element.Uri)); 26 } 27 } 28 29 async void OnWebViewNavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args) 30 { 31 if (args.IsSuccess) 32 { 33 // Inject JS script 34 await Control.InvokeScriptAsync("eval", new[] { JavaScriptFunction }); 35 } 36 } 37 38 void OnWebViewScriptNotify(object sender, NotifyEventArgs e) 39 { 40 Element.InvokeAction(e.Value); 41 } 42 } 43 }
HybridWebViewRenderer类加载将HybridWebView.Uri 属性中指定的web页面加载到一个原生WebView控件中,并且在web页面加载后,通过WebView.InvokeScriptAsync方法将invokeCSharpAction JavaScript函数注入到web页面中。一旦用户输入他们的名字并点击HTML按钮元素,invokeCSharpAction JavaScript函数就会被执行,当从web页面接收到通知后,将调用OnWebViewScriptNotify方法。反过来,该方法调用混合HybridWebView.InvokeAction方法,它将调用注册的操作以显示弹出窗口。
以上是关于Xamarin.Forms自定义用户界面控件实现一个HybridWebView(混合webview)的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin.forms 自定义dropdownview控件
ReactiveUI 中的 Xamarin.Forms 控件是不是需要自定义绑定?