flutter中 webview在iOS中其實是用了原生的wkwebview,所以,flutter與js的交互跟iOS原生的wkwebview與js的交互特別相似,只是叫法不同。
本文采用Flutter官方WebView插件:https://pub.dartlang.org/packages/webview_flutter
另外一個插件:叫做flutter_webview_plugin,需要在"pubspec.yaml"文件中添加“flutter_webview_plugin: ^0.3.5”,并點擊右上部的“packages get”。不過這個插件中好像對這種交互不太友好,Flutter調用JS可以,但是JS怎么調用Flutter還沒找到。
開始之前先簡單了解一下官方WebView所包含的API:
-
onWebViewCreated
:在WebView創建完成后調用,只會被調用一次; -
initialUrl
:初始load的url; -
javascriptMode
:JS執行模式(是否允許JS執行); -
javascriptChannels
:JS和Flutter通信的Channel; -
navigationDelegate
:路由委托(可以通過在此處攔截url實現JS調用Flutter部分); -
gestureRecognizers
:手勢監聽; -
onPageFinished
:WebView加載完畢時的回調。
JS調用Flutter
JS調用Flutter有兩種方法:使用javascriptChannels發送消息
和使用路由委托(navigationDelegate)攔截url
。
方法1:使用javascriptChannels發送消息
javascriptChannels
參數可以傳入一組Channels,我們可以定義一個_alertJavascriptChannel變量
,這個channel用來控制JS調用Flutter的toast功能:
JavascriptChannel _alertJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toast',
onMessageReceived: (JavascriptMessage message) {
showToast(message.message);
});
}
WebView(
javascriptChannels: <JavascriptChannel>[
_alertJavascriptChannel(context),
].toSet(),
在上面的代碼中,我們定義了一個_alertJavascriptChannel變量
,并給它起了個name叫Toast
,這個name屬性接收的是一個字符串,它代表了JS調用Flutter時,雙方共同商定好了的一個協議,JS通過這個name去post對應的信息給Flutter(API為name.postMessage('xxxxxx')
)。我們在網頁部分寫一個簡單的button,點擊后開始JS調用Flutter的邏輯:
<button onclick="callFlutter()">callFlutter</button>
function callFlutter(){
Toast.postMessage("JS調用了Flutter");
}
onMessageReceived
為Flutter接收到了JS的消息之后的回調,我們可以通過message.message
來獲取JS發給我們的消息內容。JavascriptMessage
類暫時只有一個String類型的message成員變量,所以如果需要傳遞復雜數據,可以通過傳遞json字符串來解決。
代碼重點:JavascriptChannel中的name要與JS中的name.postMessage()相對應!!
方法2:使用路由委托navigationDelegate攔截url
navigationDelegate
回調在每次網頁路由地址發生變化的時候都會觸發,因此我們可以攔截特定的url來實現JS調用Flutter。
同樣的,我們在網頁部分寫一個簡單的button,點擊后跳轉路由"js://webview?arg1=111&args2=222"
。我們可以和客戶端協商好一個scheme,比如這個例子里面就是js://webview
,我們可以在query string上帶上我們想要傳遞的參數:
<button onclick="callFlutter()">callFlutter</button>
function callFlutter(){
/*約定的url協議為:js://webview?arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&args2=222";
}
在Flutter端,我們就可以在navigationDelegate
回調中攔截這個符合js://webview
scheme的路由地址了:
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('js://webview')) {
showToast('JS調用了Flutter By navigationDelegate');
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
我們通過return不同的值,告訴WebView怎么處理這個路由:
-
NavigationDecision.prevent
:阻止路由替換; -
NavigationDecision.navigate
:允許路由替換。
Flutter調用JS
在WebView創建完成之后,我們可以拿到一個WebViewController,通過它的evaluateJavascript()
方法,我們可以執行JS語句:
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
},
······
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller
?.evaluateJavascript('callJS("visible")')
?.then((result) {
// You can handle JS result here.
});
},
child: Text('call JS'),
),
<p id="p1" style="visibility:hidden;">
Flutter 調用了 JS.
Flutter 調用了 JS.
Flutter 調用了 JS.
</p>
function callJS(message){
document.getElementById("p1").style.visibility = message;
}
在上面的例子中,我們點擊floatingActionButton后,就會去執行JS中的callJS()
方法了,具體UI體現為:將隱藏的段落重新顯示。evaluateJavascript()
返回值是一個Future,因此我們可以接收JS給我們的返回值,返回值格式請閱讀官方API注釋。
這里要注意的是,evaluateJavascript()
方法,Flutter建議我們在onPageFinished
回調之后去執行,以保證所有的HTML都已經加載完畢了。因此在實際開發中,我這里展示的這種直接將onWebViewCreated
中的controller賦值的方法是不可取的,應該是使用FutureBuilder
之類的方式去實現比較優雅。