自訂 WebView
Xamarin.FormsWebView
是在您的應用程式中顯示 Web 和 HTML 內容的檢視。 本文說明如何建立自定義轉譯器,以擴充 WebView
以允許從 JavaScript 叫用 C# 程序代碼。
每個 Xamarin.Forms 檢視都有每個平臺的隨附轉譯器,可建立原生控件的實例。 WebView
當 應用程式在 Xamarin.Forms iOS 上轉譯 時,類別WkWebViewRenderer
就會具現化,進而具現化原生WkWebView
控件。 在 Android 平台上,WebViewRenderer
類別會具現化原生 WebView
控制項。 在通用 Windows 平台 (UWP) 上,WebViewRenderer
類別會具現化原生 WebView
控制項。 如需控件對應之轉譯器和原生控件類別 Xamarin.Forms 的詳細資訊,請參閱 轉譯器基類和原生控件。
下圖說明 View
和實作它之對應原生控制項間的關聯性:
轉譯程式可用來實作平臺自定義,方法是在每個平臺上建立的 WebView
自定義轉譯器。 執行這項作業的程序如下:
現在將討論每個專案,以實 HybridWebView
作可增強 Xamarin.FormsWebView
的轉譯器,以允許從 JavaScript 叫用 C# 程式代碼。 HybridWebView
執行個體會用以顯示 HTML 網頁,要求使用者輸入其名稱。 然後,當使用者按一下 HTML 按鈕時,JavaScript 函式會叫用 C# Action
,其顯示包含使用者名稱的快顯視窗。
如需從 JavaScript 叫用 C# 的程式詳細資訊,請參閱 從 JavaScript 叫用 C# 。 如需 HTML 頁面的詳細資訊,請參閱 建立網頁。
注意
WebView
可以從 C# 叫用 JavaScript 函式,並將任何結果傳回呼叫的 C# 程式代碼。 如需詳細資訊,請參閱 叫用 JavaScript。
建立 HybridWebView
自訂 HybridWebView
控制項可以藉由子類別 WebView
化來建立:
public class HybridWebView : WebView
{
Action<string> action;
public static readonly BindableProperty UriProperty = BindableProperty.Create(
propertyName: "Uri",
returnType: typeof(string),
declaringType: typeof(HybridWebView),
defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
action = callback;
}
public void Cleanup()
{
action = null;
}
public void InvokeAction(string data)
{
if (action == null || data == null)
{
return;
}
action.Invoke(data);
}
}
HybridWebView
自訂控制項會在 .NET Standard 程式庫專案中建立,並定義下列控制項的 API:
- 指定要載入之網頁位址的
Uri
屬性。 - 向控制項登錄
Action
的RegisterAction
方法。 已註冊的動作會從 JavaScript 叫用,此 JavaScript 包含在透過Uri
屬性參考的 HTML 檔案中。 - 移除已註冊
Action
之參考的CleanUp
方法。 - 叫用已註冊
Action
的InvokeAction
方法。 這個方法將會從每個平台專案中的自定義轉譯器呼叫。
使用 HybridWebView
您可以宣告控制項的位置命名空間並使用自訂控制項上的命名空間前置詞,在 .NET Standard 程式庫專案的 XAML 中參考 HybridWebView
自訂控制項。 下列程式碼範例示範 XAML 頁面如何使用 HybridWebView
自訂控制項:
<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
x:Class="CustomRenderer.HybridWebViewPage"
Padding="0,40,0,0">
<local:HybridWebView x:Name="hybridWebView"
Uri="index.html" />
</ContentPage>
local
命名空間前置詞沒有命名限制。 不過,clr-namespace
和 assembly
值必須符合自訂控制項的詳細資料。 一旦宣告命名空間,即會使用前置詞來參考自訂控制項。
下列程式碼範例示範 C# 頁面如何使用 HybridWebView
自訂控制項:
public HybridWebViewPageCS()
{
var hybridWebView = new HybridWebView
{
Uri = "index.html"
};
// ...
Padding = new Thickness(0, 40, 0, 0);
Content = hybridWebView;
}
HybridWebView
執行個體會用於顯示每個平台的原生 Web 控制項。 Uri
屬性會設定為儲存在每個平台專案中的 HTML 檔案,並由原生 Web 控件顯示。 轉譯的 HTML 會要求使用者輸入其名稱,以 JavaScript 函式叫用 C# Action
以回應 [HTML] 按鈕的按一下動作。
HybridWebViewPage
註冊要從 JavaScript 叫用的動作,如下列程式碼範例所示:
public partial class HybridWebViewPage : ContentPage
{
public HybridWebViewPage()
{
// ...
hybridWebView.RegisterAction(data => DisplayAlert("Alert", "Hello " + data, "OK"));
}
}
這個動作會呼叫 DisplayAlert
方法,顯示強制回應快顯,此快顯可呈現 HybridWebView
執行個體所顯示之 HTML 頁面中輸入的名稱。
自定義轉譯器現在可以新增至每個應用程式專案,藉由允許從 JavaScript 叫用 C# 程式代碼來增強平臺 Web 控件。
在每個平臺上建立自定義轉譯器
建立自訂轉譯器類別的流程如下:
- 在 iOS 上建立 類別的
WkWebViewRenderer
子類別,以及在WebViewRenderer
Android 和 UWP 上建立類別,以轉譯自定義控件。 - 覆寫轉
OnElementChanged
譯WebView
和 寫入邏輯的方法,以自定義它。 建立物件時HybridWebView
會呼叫這個方法。 ExportRenderer
將屬性新增至自定義轉譯器類別或AssemblyInfo.cs,以指定將用來轉Xamarin.Forms譯自定義控件。 這個屬性是用來向 Xamarin.Forms註冊自定義轉譯器。
注意
對於大多數 Xamarin.Forms 元素,您可以選擇在每個平台專案中提供自定義轉譯器。 如果自訂轉譯器未註冊,則會使用控制項基底類別的預設轉譯器。 不過,轉譯 View 項目時,每個平台專案都必須要有自訂轉譯器。
下圖說明應用程式範例中每個專案的責任,以及這些專案之間的關聯性:
自定義 HybridWebView
控件是由平台轉譯器類別轉譯,其衍生自 WkWebViewRenderer
iOS 上的 類別,以及 WebViewRenderer
Android 和 UWP 上的 類別。 這會導致使用原生 Web 控件轉譯每個 HybridWebView
自定義控件,如下列螢幕快照所示:
WkWebViewRenderer
和 WebViewRenderer
類別會公開 OnElementChanged
方法,這個方法會在建立自定義控件以轉譯對應的原生Web控件時Xamarin.Forms呼叫。 這個方法會採用 VisualElementChangedEventArgs
包含 OldElement
和 NewElement
屬性的參數。 這些屬性代表Xamarin.Forms轉譯器所附加的專案,以及Xamarin.Forms轉譯器分別附加至的專案。 在範例應用程式中,OldElement
屬性會是 null
,而 NewElement
屬性會包含 HybridWebView
執行個體的參考。
在每個平台轉譯器類別中,方法的 OnElementChanged
覆寫版本是執行原生 Web 控件自定義的位置。 可以透過屬性取得所轉譯之控件的Element
參考Xamarin.Forms。
每個自定義轉譯器類別都會以 ExportRenderer
向 註冊轉譯器 Xamarin.Forms的屬性裝飾。 屬性會採用兩個參數 :要轉譯之 Xamarin.Forms 自定義控件的類型名稱,以及自定義轉譯器的型別名稱。 屬性的 assembly
前置詞會指定套用至整個組件的屬性。
下列各節將討論每個原生 Web 控制項所載入的網頁結構、從 JavaScript 叫用 C# 的程式,以及每個平臺自定義轉譯器類別的實作。
建立網頁
下列程式碼範例示範將由 HybridWebView
自訂控制項顯示的網頁:
<html>
<body>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<h1>HybridWebView Test</h1>
<br />
Enter name: <input type="text" id="name">
<br />
<br />
<button type="button" onclick="javascript: invokeCSCode($('#name').val());">Invoke C# Code</button>
<br />
<p id="result">Result:</p>
<script type="text/javascript">function log(str) {
$('#result').text($('#result').text() + " " + str);
}
function invokeCSCode(data) {
try {
log("Sending Data:" + data);
invokeCSharpAction(data);
}
catch (err) {
log(err);
}
}</script>
</body>
</html>
網頁可讓使用者在 input
項目中輸入他們的姓名,並提供按一下即可叫用 C# 程式碼的 button
項目。 執行這項作業的程序如下:
- 當使用者按一下
button
項目時,即會呼叫invokeCSCode
JavaScript 函式,並將input
項目的值傳遞至函式。 invokeCSCode
函式會呼叫log
函式,顯示它傳送到 C#Action
的資料。 然後,它會呼叫invokeCSharpAction
方法來叫用 C#Action
,傳遞從input
項目收到的參數。
invokeCSharpAction
JavaScript 函式不是在網頁中定義,而是由每個自訂轉譯器插入至網頁。
在 iOS 上,此 HTML 檔案位於具有 BundleResource 建置動作的平台專案 [內容] 資料夾中。 在 Android 上,此 HTML 檔案則位於具有 AndroidAsset 建置動作的平台專案 [資產/內容] 資料夾中。
從 JavaScript 叫用 C#
每個平台從 JavaScript 叫用 C# 的程序都完全相同:
- 自訂轉譯器會建立原生 Web 控制項,並載入
HybridWebView.Uri
屬性指定的 HTML 檔案。 - 一旦載入網頁,自訂轉譯器就會將
invokeCSharpAction
JavaScript 函式插入至網頁。 - 當使用者輸入其名稱並按一下 HTML
button
項目時,即會叫用invokeCSCode
函式,然後接著叫用invokeCSharpAction
函式。 invokeCSharpAction
函式會在自訂轉譯器中叫用方法,再接著叫用HybridWebView.InvokeAction
方法。HybridWebView.InvokeAction
方法叫用已註冊的Action
。
下列各節會討論如何在每個平台上實作此程序。
在 iOS 上建立自定義轉譯器
下列程式碼範例示範適用於 iOS 平台的自訂轉譯器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.iOS
{
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
WKUserContentController userController;
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
userController = config.UserContentController;
var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "invokeAction");
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
userController.RemoveAllUserScripts();
userController.RemoveScriptMessageHandler("invokeAction");
HybridWebView hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
string filename = Path.Combine(NSBundle.MainBundle.BundlePath, $"Content/{((HybridWebView)Element).Uri}");
LoadRequest(new NSUrlRequest(new NSUrl(filename, false)));
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
((HybridWebView)Element).InvokeAction(message.Body.ToString());
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer
類別會將 HybridWebView.Uri
屬性中所指定網頁載入到原生的 WKWebView
控制項中,而 invokeCSharpAction
JavaScript 函式會插入至網頁。 一旦使用者輸入其名稱並按一下 HTML button
項目,即會使用網頁收到訊息後呼叫的 DidReceiveScriptMessage
方法來執行 invokeCSharpAction
JavaScript 函式。 接著,此方法會叫用 HybridWebView.InvokeAction
方法,這樣會叫用已註冊的動作來顯示快顯。
執行此功能的程序如下:
- 轉譯器建構函式會
WkWebViewConfiguration
建立 物件,並擷取其WKUserContentController
物件。 對象WkUserContentController
允許張貼訊息,並將使用者腳本插入網頁。 - 轉譯器建構函式會
WKUserScript
建立 物件,在載入網頁之後,將JavaScript函式插入invokeCSharpAction
網頁。 - 轉譯器建構函式會呼叫
WKUserContentController.AddUserScript
方法,將 物件新增WKUserScript
至內容控制器。 - 轉譯器建構函式會呼叫
WKUserContentController.AddScriptMessageHandler
方法,將名為invokeAction
的腳本訊息處理程式新增至WKUserContentController
物件,這會導致 JavaScript 函window.webkit.messageHandlers.invokeAction.postMessage(data)
式定義於所有使用WKUserContentController
物件之實例的所有WebView
框架中。 - 前提是自定義轉譯器已附加至新 Xamarin.Forms 元素:
WKWebView.LoadRequest
方法會載入HybridWebView.Uri
屬性所指定的 HTML 檔案。 程式碼指定該檔案會儲存在專案的Content
資料夾中。 一旦顯示網頁,即將invokeCSharpAction
JavaScript 函式插入至網頁。
- 當轉譯器附加至變更的專案時,就會釋放資源。
- 處置轉譯器時,會 Xamarin.Forms 清除專案。
注意
僅 iOS 8 和更新版本支援 WKWebView
類別。
此外,必須更新 Info.plist 以包含下列值:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
在Android上建立自定義轉譯器
下列程式碼範例示範適用於 Android 平台的自訂轉譯器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.Droid
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavascriptFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
Context _context;
public HybridWebViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
((HybridWebView)Element).Cleanup();
}
if (e.NewElement != null)
{
Control.SetWebViewClient(new JavascriptWebViewClient(this, $"javascript: {JavascriptFunction}"));
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl($"file:///android_asset/Content/{((HybridWebView)Element).Uri}");
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer
類別會將 HybridWebView.Uri
屬性中所指定網頁載入到原生的 WebView
控制項中,並在網頁載入完成後,使用 JavascriptWebViewClient
類別的 OnPageFinished
覆寫將 invokeCSharpAction
JavaScript 函式插入至網頁:
public class JavascriptWebViewClient : FormsWebViewClient
{
string _javascript;
public JavascriptWebViewClient(HybridWebViewRenderer renderer, string javascript) : base(renderer)
{
_javascript = javascript;
}
public override void OnPageFinished(WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
一旦使用者輸入其名稱並按一下 HTML button
項目,即會執行 invokeCSharpAction
JavaScript 函式。 執行此功能的程序如下:
- 前提是自定義轉譯器已附加至新 Xamarin.Forms 元素:
- 方法會將
SetWebViewClient
新的JavascriptWebViewClient
物件設定為的實作WebViewClient
。 WebView.AddJavascriptInterface
方法會將新的JSBridge
執行個體插入至 WebView JavaScript 內容的主框架,將它命名為jsBridge
。 以便從 JavaScript 存取JSBridge
類別中的方法。WebView.LoadUrl
方法會載入HybridWebView.Uri
屬性所指定的 HTML 檔案。 程式碼指定該檔案會儲存在專案的Content
資料夾中。- 在
JavascriptWebViewClient
類別中,invokeCSharpAction
JavaScript 函式會在頁面載入完成後插入至網頁。
- 方法會將
- 當轉譯器附加至變更的專案時,就會釋放資源。
- 處置轉譯器時,會 Xamarin.Forms 清除專案。
當 invokeCSharpAction
JavaScript 函式執行時,它會依序叫用 JSBridge.InvokeAction
方法,如下列程式碼範例所示:
public class JSBridge : Java.Lang.Object
{
readonly WeakReference<HybridWebViewRenderer> hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if (hybridWebViewRenderer != null && hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
((HybridWebView)hybridRenderer.Element).InvokeAction(data);
}
}
}
類別必須衍生自 Java.Lang.Object
,而向 JavaScript 公開的方法則必須使用 [JavascriptInterface]
和 [Export]
屬性裝飾。 因此,當 invokeCSharpAction
JavaScript 函式插入至網頁且執行時,它會因為正使用 [JavascriptInterface]
和 [Export("invokeAction")]
屬性裝飾而呼叫 JSBridge.InvokeAction
方法。 接著, InvokeAction
方法會叫 HybridWebView.InvokeAction
用 方法,這會叫用已註冊的動作以顯示快顯。
重要
使用 屬性的 [Export]
Android 項目必須包含 的 Mono.Android.Export
參考,否則會產生編譯程序錯誤。
請注意,JSBridge
類別會將 WeakReference
維持為 HybridWebViewRenderer
類別。 這是為了避免建立兩個類別之間的循環參考。 如需詳細資訊,請參閱 弱式參考。
在UWP上建立自定義轉譯器
下列程式碼範例示範適用於 UWP 的自訂轉譯器:
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace CustomRenderer.UWP
{
public class HybridWebViewRenderer : WebViewRenderer
{
const string JavaScriptFunction = "function invokeCSharpAction(data){window.external.notify(data);}";
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
Control.NavigationCompleted -= OnWebViewNavigationCompleted;
Control.ScriptNotify -= OnWebViewScriptNotify;
}
if (e.NewElement != null)
{
Control.NavigationCompleted += OnWebViewNavigationCompleted;
Control.ScriptNotify += OnWebViewScriptNotify;
Control.Source = new Uri($"ms-appx-web:///Content//{((HybridWebView)Element).Uri}");
}
}
async void OnWebViewNavigationCompleted(Windows.UI.Xaml.Controls.WebView sender, WebViewNavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// Inject JS script
await Control.InvokeScriptAsync("eval", new[] { JavaScriptFunction });
}
}
void OnWebViewScriptNotify(object sender, NotifyEventArgs e)
{
((HybridWebView)Element).InvokeAction(e.Value);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
((HybridWebView)Element).Cleanup();
}
base.Dispose(disposing);
}
}
}
HybridWebViewRenderer
類別會將 HybridWebView.Uri
屬性中所指定網頁載入到原生的 WebView
控制項中,並在網頁載入後,使用 WebView.InvokeScriptAsync
方法將 invokeCSharpAction
JavaScript 函式插入至網頁。 一旦使用者輸入其名稱並按一下 HTML button
項目,即會使用網頁收到通知後呼叫的 OnWebViewScriptNotify
方法來執行 invokeCSharpAction
JavaScript 函式。 接著,此方法會叫用 HybridWebView.InvokeAction
方法,這樣會叫用已註冊的動作來顯示快顯。
執行此功能的程序如下:
- 前提是自定義轉譯器已附加至新 Xamarin.Forms 元素:
- 則會註冊
NavigationCompleted
和ScriptNotify
事件的事件處理常式。 當原生WebView
控制項已完成載入目前的內容或導覽失敗時,會引發NavigationCompleted
事件。 當原生WebView
控制項使用 JavaScript 將字串傳遞至應用程式時,會引發ScriptNotify
事件。 網頁在傳遞string
參數時呼叫window.external.notify
,藉以引發ScriptNotify
事件。 WebView.Source
屬性設為HybridWebView.Uri
屬性所指定之 HTML 檔案的 URI。 程式碼假設該檔案會儲存在專案的Content
資料夾中。 一旦顯示網頁,就會引發NavigationCompleted
事件並叫用OnWebViewNavigationCompleted
方法。 然後使用WebView.InvokeScriptAsync
方法將invokeCSharpAction
JavaScript 函式插入至網頁,假設導覽已順利完成。
- 則會註冊
- 當轉譯器附加至變更的專案時,事件會取消訂閱。
- 處置轉譯器時,會 Xamarin.Forms 清除專案。