Flutter-webview的使用和JS交互
日常我们在开发项目时,为了项目快速的开发和迭代,难免会用到H5页面。使用Flutter进行项目开发时,也一样免不了要加载H5页面,在移动开发中打开H5页面需要使用WebView
组件。同时,为了和H5页面进行数据交换,有时候还需要借助JSBridge
来实现客户端与H5之间的通讯。
添加 webview_flutter 组件
在项目的 pubspec.yaml
文件中添加依赖:webview_flutter: ^3.0.0
,然后执行 pub get
。
- 由于加载WebView需要使用网络,所以还需要在android中添加网络权限。打开目录android/app/src/main/AndroidManifest.xml,然后添加如下代码即可。
<uses-permission android:name="android.permission.INTERNET"/>
- 由于iOS在9.0版本默认开启了Https,所以要运行Http的网页,还需要在ios/Runner/Info.plist文件中添加如下代码。
<key>io.flutter.embedded_views_preview</key>
<string>YES</string>
webview_flutter 组件的构造方法的简单介绍
WebView({
Key key,
this.onWebViewCreated, //WebView创建完成之后的回调
this.initialUrl, // 初始化 URL
this.javascriptMode = JavascriptMode.disabled, //JS执行模式,默认是不调用
this.javascriptChannels, // JS可以调用Flutter 的通道
this.navigationDelegate, // 路由委托,可以使用它执行拦截操作
this.gestureRecognizers, // 手势监听相关
this.onPageStarted, //开始加载页面回调
this.onPageFinished, // 页面加载完成的回调
this.onWebResourceError, //资源加载失败回调
this.debuggingEnabled = false,
this.gestureNavigationEnabled = false,
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
})
使用 Webview 加载网页时,很多时候需要与JS进行交互,即JS调用Flutter
和Flutter调用JS
。
查看官方文档,发现提供的能力,在实际开发中可以参考这些功能。包含:网络请求、Cookies相关、缓存相关、加载html、加载失败页面
等等。
Future<void> _onShowUserAgent(
WebViewController controller, BuildContext context) async {
// Send a message with the user agent string to the Toaster JavaScript channel we registered
// with the WebView.
await controller.runJavascript(
'Toaster.postMessage("User Agent: " + navigator.userAgent);');
}
Future<void> _onListCookies(
WebViewController controller, BuildContext context) async {
final String cookies =
await controller.runJavascriptReturningResult('document.cookie');
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(SnackBar(
content: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Cookies:'),
_getCookieList(cookies),
],
),
));
}
Future<void> _onAddToCache(
WebViewController controller, BuildContext context) async {
await controller.runJavascript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";');
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text('Added a test entry to cache.'),
));
}
Future<void> _onListCache(
WebViewController controller, BuildContext context) async {
await controller.runJavascript('caches.keys()'
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
'.then((caches) => Toaster.postMessage(caches))');
}
Future<void> _onClearCache(
WebViewController controller, BuildContext context) async {
await controller.clearCache();
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(const SnackBar(
content: Text('Cache cleared.'),
));
}
Future<void> _onClearCookies(BuildContext context) async {
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
// ignore: deprecated_member_use
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(message),
));
}
Future<void> _onNavigationDelegateExample(
WebViewController controller, BuildContext context) async {
final String contentBase64 =
base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
await controller.loadUrl('data:text/html;base64,$contentBase64');
}
Future<void> _onSetCookie(
WebViewController controller, BuildContext context) async {
await CookieManager().setCookie(
const WebViewCookie(
name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything'),
);
await controller.loadUrl('https://httpbin.org/anything');
}
Future<void> _onDoPostRequest(
WebViewController controller, BuildContext context) async {
final WebViewRequest request = WebViewRequest(
uri: Uri.parse('https://httpbin.org/post'),
method: WebViewRequestMethod.post,
headers: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},
body: Uint8List.fromList('Test Body'.codeUnits),
);
await controller.loadRequest(request);
}
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadHtmlStringExample(
WebViewController controller, BuildContext context) async {
await controller.loadHtmlString(kLocalExamplePage);
}
Future<void> _onTransparentBackground(
WebViewController controller, BuildContext context) async {
await controller.loadHtmlString(kTransparentBackgroundPage);
}
Widget _getCookieList(String cookies) {
if (cookies == null || cookies == '""') {
return Container();
}
final List<String> cookieList = cookies.split(';');
final Iterable<Text> cookieWidgets =
cookieList.map((String cookie) => Text(cookie));
return Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: cookieWidgets.toList(),
);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File(
<String>{tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator));
await indexFile.create(recursive: true);
await indexFile.writeAsString(kLocalExamplePage);
return indexFile.path;
}
JS调用Flutter
navigationDelegate 方式实现
这种方式实现的原理主要是加载网页的时候进行拦截
。
下面举例实现的代码
js代码:
document.location = "js://webview?name=candy";
flutter 端代码:
navigationDelegate: (NavigationRequest request) {
if(request.url.startsWith("js://webview")) {
print("开始处理 ${request.url}");
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
这里的 NavigationDecision.prevent
表示阻止路由替换,NavigationDecision.navigate
表示允许路由替换。
javascriptChannels 方式实现
js代码:
<button onclick="callFlutter()">callFlutter</button>
function callFlutter(){
Toast.postMessage("js call flutter");
}
flutter 端代码:
WebView(
javascriptChannels: <JavascriptChannel>[
_shareJavascriptChannel(context),
].toSet(),
)
JavascriptChannel _shareJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'share',
onMessageReceived: (JavascriptMessage message) {
print("参数: ${message.message}");
showToast(message.message);
});
}
Flutter 调用 JS
js代码:
function callJS(message){
document.getElementById("p1").style.visibility = message;
}
Flutter 代码
Future<void> evaluateJavascript() async {
print('evaluateJavascript');
_controller.runJavascript('callJS('visible');');
}
加载本地 html
html 代码
<!DOCTYPE html>
<html>
<body>
<style>*{font-size:50px;}</style>
<button onclick="callFlutter()">callFlutter</button>
<p id="p1" style="visibility:hidden;">
Flutter代码调用了JS方法.
</p>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="http://cdn.amazeui.org/amazeui/2.5.0/js/amazeui.min.js"></script>
<script type="text/javascript">
function callJS(message){
document.getElementById("p1").style.visibility = message;
}
</script>
<script type="text/javascript">
function callFlutter(){
Toaster.postMessage('js call flutter');
}
</script>
</body>
</html>
Future<void> _loadHtmlFromAsset() async {
String html = 'assets/static/test.html';
final String path = await rootBundle.loadString(html);
_controller.loadUrl(Uri.dataFromString(path,
mimeType: 'text/html', encoding: Encoding.getByName('utf-8'))
.toString());
}
完整实现代码
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget {
@override
_WebViewPageState createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> {
late WebViewController _controller;
String _title = "webview";
//加载Html
Future<void> _loadHtmlFromAsset() async {
String html = 'assets/static/test.html';
final String path = await rootBundle.loadString(html);
_controller.loadUrl(Uri.dataFromString(path,
mimeType: 'text/html', encoding: Encoding.getByName('utf-8'))
.toString());
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("$_title"),
),
child: SafeArea(
child: WebView(
//initialUrl: "https://flutterchina.club/",
//JS执行模式 是否允许JS执行
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (controller) {
_controller = controller;
_loadHtmlFromAsset();
},
onPageFinished: (url) async{
//调用JS方法,获取页面的标题
String title = await _controller.runJavascriptReturningResult('document.title');
setState(() {
_title = title;
});
evaluateJavascript();
},
navigationDelegate: (NavigationRequest request) {
if(request.url.startsWith("js://webview")) {
print("开始处理 ${request.url}");
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
javascriptChannels: <JavascriptChannel>{
JavascriptChannel(
name: "share",
onMessageReceived: (JavascriptMessage message) {
print("参数: ${message.message}");
//实际应用中要通过map通过key获取
String callbackname = message.message;
String data = "收到消息调用了";
String script = "$callbackname($data)";
_controller.runJavascript(script);
}
),
},
),
),
);
}
Future<void> evaluateJavascript() async {
print('evaluateJavascript');
//这个是实现了Flutter控制了H5页面文本的显示
_controller.runJavascript('callJS('visible');');
}
}
加载网页
给这个属性赋值就可以了
initialUrl: 'https://flutterchina.club/',
页面是否可以回退
Future<bool> _goBack(BuildContext context) async {
if (_controller != null && await _controller.canGoBack()) {
_controller.goBack();
return false;
}
return true;
}
官方实现了更多的功能:pub.dev/packages/we…
以上就讲解了 webView 的网页加载、JS交互、网络请求、Cookies相关、缓存相关、加载html、加载失败页面等功能。
转载自:https://juejin.cn/post/7051765106098569224