Xamarin.Forms自定義用戶界面控件實現一個HybridWebView(混合webview)


原文: Implementing a HybridWebView
呈現一個特定於平台的視圖
Xamarin.Forms自定義用戶界面控件應該來自視圖類(View class),用於在屏幕上放置布局和控件。本文演示了如何為HybridWebView(混合webview)自定義控件創建自定義渲染器,該控件演示了如何增強特定平台的web控件,以允許從JavaScript調用c#代碼。
每一個Xamarin.Forms視圖為每個創建本地控件實例的平台提供了相應的渲染器。當一個視圖被Xamarin.Forms在iOS中渲染時,ViewRenderer類被實例化,從而實例化一個本機的UIView控件。
在Android平台上,ViewRenderer類實例化一個視圖控件。在Windows Phone和Universal Windows平台(UWP)上,ViewRenderer類實例化了一個本機FrameworkElement控件。有關使用Xamarin.Forms控件映射到渲染器和本地控件類的更多信息,請參考: Renderer Base Classes and Native Controls(渲染器基類和本地控件)。
下面的圖表說明了視圖與實現它的相應本機控件之間的關系:
通過為每個平台上的視圖創建自定義渲染器,可以使用呈現過程實現特定於平台的定制。這樣做的過程如下:
1.創建HybridWebView自定義控件。
2.使用來自Xamarin.Forms的HybridWebView。
3.在每個平台上為HybridWebView創建自定義渲染器。
現在,每個項目都將依次討論,以實現一個HybridWebView渲染器,它增強了特定於平台的web控件,以允許從JavaScript調用c#代碼。HybridWebView實例將被用來顯示一個HTML頁面,讓用戶輸入他們的名字。然后,當用戶單擊HTML按鈕時,一個JavaScript函數將調用一個C#操作,該操作將顯示一個包含用戶名的彈出窗口。
有關從JavaScript調用C#的過程的更多信息,請參見 Invoking C# from JavaScript(從JavaScript調用C#)。有關HTML頁面的更多信息,請參見 Creating the Web Page(創建Web頁面)。
創建HybridWebView
通過子類化 View類,可以創建HybridWebView自定義控件,如下面的代碼示例所示:
 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:

●一個Uri屬性,用於指定要加載web頁面的地址。
●一個RegisterAction方法,用控件注冊一個動作。通過Uri屬性引用的HTML文件中包含的JavaScript調用注冊的動作。
●一個CleanUp方法,移除注冊 Action 的引用。
●一個InvokeAction方法,調用注冊動作。這個方法將從每個平台特定項目中的自定義渲染器調用。
使用HybridWebView
通過為其位置聲明一個命名空間並使用自定義控件上的命名空間前綴,可以在PCL項目中引用XAML中的HybridWebView自定義控件。下面的代碼示例展示了如何使用XAML頁面來使用HybridWebView自定義控件:
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命名空間和程序集值必須與自定義控件的細節相匹配。聲明命名空間后,將使用前綴引用自定義控件。

下面的代碼示例展示了如何通過C#頁面來使用HybridWebView自定義控件:
 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#操作。

HybridWebViewPage注冊從JavaScript調用的操作,如下面的代碼示例所示:
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頁面中的名稱。

現在可以將自定義渲染器添加到每個應用程序項目中,以便通過允許從JavaScript調用c#代碼來增強特定於平台的web控件。
 
在每個平台上創建自定義渲染器
創建自定義渲染類的過程如下:
1.創建ViewRenderer < T1,T2 >類,以呈現自定義控件的子類。
第一個類型參數應該是自定義控件,在本例中是HybridWebView。
第二個類型參數應該是實現自定義視圖的本機控件。
2.重寫顯示自定義控件的OnElementChanged方法,並編寫邏輯來自定義它。
這個方法在相應的Xamarin.Forms自定義控件創建時調用。
3.向自定義渲染器類添加一個ExportRenderer屬性,以指定它將用於呈現Xamarin.Forms自定義控件。此屬性用於將自定義渲染器與Xamarin.Forms進行注冊。
對大多數Xamarin.Forms元素,可以在每個平台項目中提供自定義渲染器。如果未注冊自定義渲染器,則將使用控件基類的默認渲染器。然而,在呈現視圖元素時,每個平台項目都需要自定義渲染器。
下圖說明了示例應用程序中每個項目的職責,以及它們之間的關系:
HybridWebView自定義控件是由特定於平台的呈現類提供的,這些類都來自於每個平台的ViewRendererclass。這個結果在每個HybridWebView的自定義控件中都顯示了特定於平台的web控件,如下面的截圖所示:
ViewRenderer類公開了OnElementChanged方法,它是在創建Xamarin.Forms自定義控件時調用的,以呈現相應的本機web控件。該方法采用ElementChangedEventArgs參數,其中包含了OldElement和NewElement屬性。這些屬性表示渲染器附加到的Xamarin.Forms元素,以及渲染器所依附的Xamarin.Forms元素。在示例應用程序中,OldElement屬性將為null,NewElement屬性將包含對混合式webviewinstance的引用。
在每個平台特定的renderer類中,OnElementChanged方法的一個重寫版本是執行本機web控件實例化和自定義的位置。SetNativeControl方法應該用於實例化本機web控件,此方法還將為控件屬性分配控件引用。此外,對於正在呈現的Xamarin.Forms控件的引用可以通過元素屬性獲得。
在某些情況下,OnElementChanged方法可以多次調用,因此在實例化新的本機控件時必須小心,以防止內存泄漏。在自定義渲染器中實例化一個新的本機控件時使用的方法如下面的代碼示例所示:
 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元素時,才應該配置該控件並訂閱事件處理程序。類似地,任何訂閱到的事件處理程序都應該只在呈現元素時取消訂閱。采用這種方法將有助於創建一個不受內存泄漏影響的性能自定義渲染器。

每個自定義渲染器類都使用一個ExportRenderer屬性來修飾,該屬性將渲染器注冊為Xamarin.Forms。該屬性包含兩個參數——呈現的Xamarin.Forms自定義控件的類型名稱,以及自定義渲染器的類型名稱。屬性的裝配(assembly )前綴指定該屬性應用於整個程序集。
下面的部分將討論由每個本地web控件加載的web頁面的結構,以及從JavaScript調用C#的過程,以及在每個特定於平台的自定義renderer類中的實現。
創建Web頁面
下面的代碼示例顯示了通過混合webview自定義控件顯示的web頁面:
 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#代碼。實現這一目標的過程如下:

●當用戶單擊button元素,invokeCSCode JavaScript函數被調用時,input元素的值被傳遞給函數。
●invokeCSCode函數調用log函數,以顯示數據發送到C# Action。然后調用invokeCSharpAction方法調用C# Action,傳遞從input元素接收的參數。
invokeCSharpAction JavaScript函數沒有在web頁面中定義,並將由每個自定義渲染器注入它。
從JavaScript調用C#
從JavaScript調用C#的過程在每個平台上都是相同的:
●自定義渲染器創建一個本地網絡控制和加載指定的HTML文件HybridWebView.Uri屬性。
●一旦加載web頁面,invokeCSharpAction JavaScript函數自定義渲染器注入到web頁面。
●當用戶輸入他們的名字,點擊HTML的button元素,invokeCSCode函數被調用時,反過來調用invokeCSharpAction函數。
●invokeCSharpAction函數調用自定義渲染器的方法,進而調用HybridWebView.InvokeAction方法。
●HybridWebView.InvokeAction方法調用注冊過的Action。
 
下面的部分將討論如何在每個平台上實現這個過程。
 
在iOS上創建自定義渲染器
下面的代碼示例顯示了iOS平台的自定義渲染器:
 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 方法,它將調用注冊的操作以顯示彈出窗口。

該功能的實現如下:
●提供控制屬性為空,進行以下操作:
○WKUserContentController實例被創建時,它允許用戶發布信息和注射到web頁面的腳本。
○WKUserScript實例被創建以注入invokeCSharpAction JavaScript函數后的網頁加載web頁面。
○WKUserContentController.AddScript方法將WKUserScript實例添加到內容控制器。
WKUserContentController.AddScriptMessageHandler 方法添加一個消息處理程序腳本名為invokeAction WKUserContentController實例,這將導致JavaScript函數window.webkit.messageHandlers.invokeAction.postMessage(data)中定義的所有幀在所有web視圖將使用WKUserContentController實例。
○WKWebViewConfiguration實例被創建,與WKUserContentController實例被設置為內容的控制器。
○WKWebView控制被實例化,SetNativeControl方法被調用時指定的引用WKWebView控制控制財產。
●提供自定義渲染器是連接到一個新的Xamarin.Forms元素:
WKWebView.LoadRequest方法加載指定的HTML文件HybridWebView.Uri屬性。
該代碼指定該文件存儲在項目的內容文件夾中。
一旦web頁面顯示出來,invokeCSharpAction JavaScript函數就會被注入到web頁面中。
●當元素渲染器附加到變化:
○資源被釋放。
WKWebView類只在ios8和以后的版本中得到支持。
 
在Android上創建自定義渲染器
下面的代碼示例顯示了Android平台的自定義渲染器:
 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方法。

一旦用戶輸入他們的名字並點擊HTML按鈕元素,就會執行invokeCSharpAction JavaScript函數。
該功能的實現如下:
●提供控制屬性為空,進行以下操作:
○原生WebView實例被創建,啟用了JavaScript的控制。
○SetNativeControl方法稱為引用分配給本地WebView控件控制屬性。
●提供自定義渲染器是連接到一個新的Xamarin.Forms元素:
WebView.AddJavascriptInterface方法注入一個新的JSBridge實例主框架的WebView JavaScript上下文,JSBridge命名它。
這允許從JavaScript訪問JSBridge類中的方法。
WebView.LoadUrl方法加載指定的HTML文件HybridWebView.Uri屬性。
該代碼指定該文件存儲在項目的內容文件夾中。
○InjectJS方法是為了注入invokeCSharpAction JavaScript函數調用web頁面。
●當元素渲染器附加到變化:
○資源被釋放。
當invokeCSharpAction JavaScript函數被執行時,它依次調用JSBridge.InvokeActio方法,如下代碼示例所示:
 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方法,它將調用注冊的操作以顯示彈出窗口。

使用[Export]屬性的項目必須包含對Mono.Android.Export的引用,否則將導致編譯錯誤。
注意,JSBridge類對混合webviewrenderer類保持了弱引用。這是為了避免在兩個類之間創建循環引用。有關更多信息,請參見MSDN上的 Weak References (弱引用)。
 
在Windows Phone和UWP上創建自定義渲染器
下面的代碼示例顯示了Windows Phone和UWP的自定義渲染器:
 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方法,它將調用注冊的操作以顯示彈出窗口。

該功能的實現如下:
●提供控制屬性為空,進行以下操作:
○SetNativeControl方法實例化一個新的本地WebView控制和引用分配給控制財產。
●提供自定義渲染器是連接到一個新的Xamarin.Forms元素:
○NavigationCompleted和ScriptNotify事件的事件處理程序注冊。
當本機WebView控件已完成加載當前內容或導航失敗時,NavigationCompleted事件觸發。
當本機WebView控件中的內容使用JavaScript將字符串傳遞給應用程序時,ScriptNotify事件觸發。
在傳遞字符串參數時,web頁面通過調用window.external.notify觸發ScriptNotify事件。
○WebView.Source屬性設置為指定的HTML文件的URI HybridWebView.Uri屬性。代碼假定該文件存儲在項目的內容文件夾中。一旦web頁面顯示,NavigationCompleted事件將觸發和OnWebViewNavigationCompleted方法將被調用。
當導航成功完成時,invokeCSharpAction JavaScript函數將被注入WebView.InvokeScriptAsync方法的web頁面中。
●當元素渲染器附加到變化:
事件沒訂閱。
總結
本文演示了如何為混合視圖自定義控件創建自定義渲染器,演示了如何增強特定於平台的web控件,以允許從JavaScript調用C#代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM