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
之类的方式去实现比较优雅。