添加 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; }
官方实现了更多的功能:https://pub.dev/packages/webview_flutter/example
以上就讲解了 webView 的网页加载、JS交互、网络请求、Cookies相关、缓存相关、加载html、加载失败页面等功能。