赞
踩
提供WebView小部件的Flutter插件。在iOS上,WebView小部件由WKWebView支持; 在Android上,WebView小部件由WebView支持
dependencies:
webview_flutter: ^2.0.7
import 'package:webview_flutter/webview_flutter.dart';
WebView依靠 Platform Views将Android的Webview嵌入Flutter应用程序中。默认情况下,使用基于虚拟显示的平台视图后端,此实现具有多个 键盘。当需要键盘输入时,我们建议使用基于混合组合的平台视图实现.
在initState中
import 'dart:io'; import 'package:webview_flutter/webview_flutter.dart'; class WebViewExample extends StatefulWidget { @override WebViewExampleState createState() => WebViewExampleState(); } class WebViewExampleState extends State<WebViewExample> { @override void initState() { super.initState(); // Enable hybrid composition. if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); } @override Widget build(BuildContext context) { return WebView( initialUrl: 'https://flutter.cn', ); } }
minSdkVersion
android {
defaultConfig {
minSdkVersion 19
}
}
const WebView({ Key? key, this.onWebViewCreated, //onWebViewCreated this.initialUrl,//要显示的url this.javascriptMode = JavascriptMode.disabled,//JS执行模式 默认是不调用 this.javascriptChannels,//使用javascriptChannel JS可以调用Flutter this.navigationDelegate,//拦截请求 this.gestureRecognizers,//手势 this.onPageStarted, //页面开始加载 this.onPageFinished,//页面加载完成 this.onProgress, this.onWebResourceError, this.debuggingEnabled = false, this.gestureNavigationEnabled = false, this.userAgent, //获取设备信息,以及浏览器信息 this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, })
通过WebViewController
调用evaluateJavascript
方法,即可调用JS。
对照AgentWeb看一下
private val mWebViewClient: WebViewClient = object : WebViewClient() { override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { //do you work } } private val mWebChromeClient: WebChromeClient = object : WebChromeClient() { override fun onProgressChanged(view: WebView?, newProgress: Int) { //do you work } } override fun initView() { var title: String? = intent.getStringExtra(ACTION_TITLE) var url: String? = intent.getStringExtra(ACTION_WEB_URL) title_text.text = title if (!TextUtils.isEmpty(url)) { agentWeb = AgentWeb.with(this)// .setAgentWebParent( linearWeb, -1, LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) )//传入AgentWeb的父控件。 .useDefaultIndicator(Color.RED, 1)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。 .setWebViewClient(mWebViewClient) .setWebChromeClient(mWebChromeClient) .setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。 // .setWebViewClient(mWebViewClient)//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。 .setMainFrameErrorView( R.layout.agentweb_error_page, -1 ) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。 .setOpenOtherPageWays(DefaultWebClient.OpenOtherPageWays.DISALLOW)//打开其他页面时,弹窗质询用户前往其他应用 AgentWeb 3.0.0 加入。 .interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。 .createAgentWeb()//创建AgentWeb。 .ready()//设置 WebSettings。 .go(url); //WebView载入该url地址的页面并显示。 } }
webview中,java调用js:webView.loadUrl(“javascript:fillContent()”);
JS调用Flutter有两种方法:使用javascriptChannels发送消息
和使用路由委托(navigationDelegate)拦截url
。
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(),
;
js方法如何调用
<button οnclick="callFlutter()">callFlutter</button>
function callFlutter(){
Toast.postMessage("JS调用了Flutter");
}
onMessageReceived
为Flutter接收到了JS的消息之后的回调,我们可以通过message.message
来获取JS发给我们的消息内容。JavascriptMessage
类暂时只有一个String类型的message成员变量,所以如果需要传递复杂数据,可以通过传递json字符串来解决。
navigationDelegate
回调在每次网页路由地址发生变化的时候都会触发,因此我们可以拦截特定的url来实现JS调用Flutter。
一般不适用,常用于拦截特定Url,当然也可以监听特定的,达到js调用Flutter
NavigationDecision.prevent
:阻止路由替换;NavigationDecision.navigate
:允许路由替换。onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
},
······
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller
?.evaluateJavascript('callJS("visible")')
?.then((result) {
// You can handle JS result here.
});
},
child: Text('call JS'),
),
设置Cookie
onWebViewCreated: (WebViewController webViewController) {
_controller = webViewController;
webViewController.loadUrl(widget.url, headers: {
"Cookie":
'clientCommonlanguage=en'
});
},
将隐藏的段落重新显示。evaluateJavascript()
返回值是一个Future,因此我们可以接收JS给我们的返回值,返回值格式请阅读官方API注释,要注意Android端和iOS端的返回值格式是不一样的,Android端返回的是json字符串,iOS暂时只支持string和string格式的NSArray,其他类型数据还不支持
其中controller.canGoBack()用于判断是否能回调
FutureBuilder<WebViewController>( future: _webViewControllerFuture, builder: (BuildContext context, AsyncSnapshot<WebViewController> snapshot) { final bool webViewReady = snapshot.connectionState == ConnectionState.done; final WebViewController controller = snapshot.data; return Row( children: <Widget>[ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: !webViewReady ? null : () async { if (await controller.canGoBack()) { await controller.goBack(); } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( const SnackBar(content: Text("No back history item")), ); return; } }, ),
————————目前,webview_flutter,基本已经解决了软键盘等问题
参考一个官方案例
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; void main() => runApp(MaterialApp(home: WebViewExample())); const String kNavigationExamplePage = ''' <!DOCTYPE html><html> <head><title>Navigation Delegate Example</title></head> <body> <p> The navigation delegate is set to block navigation to the youtube website. </p> <ul> <ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul> <ul><a href="https://www.google.cn/">https://www.google.cn/</a></ul> </ul> </body> </html> '''; class WebViewExample extends StatefulWidget { @override _WebViewExampleState createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<WebViewExample> { final Completer<WebViewController> _controller = Completer<WebViewController>(); @override void initState() { super.initState(); if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: <Widget>[ NavigationControls(_controller.future), SampleMenu(_controller.future), ], ), // We're using a Builder here so we have a context that is below the Scaffold // to allow calling Scaffold.of(context) so we can show a snackbar. body: Builder(builder: (BuildContext context) { return WebView( initialUrl: 'https://www.baidu.com/', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { _controller.complete(webViewController); }, onProgress: (int progress) { print("WebView is loading (progress : $progress%)"); }, javascriptChannels: <JavascriptChannel>{ _toasterJavascriptChannel(context), //设置JS调用Flutter的方法以及方法名 }.toSet(), navigationDelegate: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { print('blocking navigation to $request}'); return NavigationDecision.prevent; } print('allowing navigation to $request'); return NavigationDecision.navigate; }, onPageStarted: (String url) { print('Page started loading: $url'); }, onPageFinished: (String url) { print('Page finished loading: $url'); }, gestureNavigationEnabled: true, ); }), floatingActionButton: favoriteButton(), ); } JavascriptChannel _toasterJavascriptChannel(BuildContext context) { return JavascriptChannel( name: 'Toaster', onMessageReceived: (JavascriptMessage message) { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }); } Widget favoriteButton() { return FutureBuilder<WebViewController>( future: _controller.future, builder: (BuildContext context, AsyncSnapshot<WebViewController> controller) { if (controller.hasData) { return FloatingActionButton( onPressed: () async { final String url = (await controller.data.currentUrl()); // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); }, child: const Icon(Icons.favorite), ); } return Container(); }); } } enum MenuOptions { showUserAgent, listCookies, clearCookies, addToCache, listCache, clearCache, navigationDelegate, } class SampleMenu extends StatelessWidget { SampleMenu(this.controller); final Future<WebViewController> controller; final CookieManager cookieManager = CookieManager(); @override Widget build(BuildContext context) { return FutureBuilder<WebViewController>( future: controller, builder: (BuildContext context, AsyncSnapshot<WebViewController> controller) { return PopupMenuButton<MenuOptions>( onSelected: (MenuOptions value) { switch (value) { case MenuOptions.showUserAgent: _onShowUserAgent(controller.data, context); break; case MenuOptions.listCookies: _onListCookies(controller.data, context); break; case MenuOptions.clearCookies: _onClearCookies(context); break; case MenuOptions.addToCache: _onAddToCache(controller.data, context); break; case MenuOptions.listCache: _onListCache(controller.data, context); break; case MenuOptions.clearCache: _onClearCache(controller.data, context); break; case MenuOptions.navigationDelegate: _onNavigationDelegateExample(controller.data, context); break; } }, itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[ PopupMenuItem<MenuOptions>( value: MenuOptions.showUserAgent, child: const Text('Show user agent'), enabled: controller.hasData, ), const PopupMenuItem<MenuOptions>( value: MenuOptions.listCookies, child: Text('List cookies'), ), const PopupMenuItem<MenuOptions>( value: MenuOptions.clearCookies, child: Text('Clear cookies'), ), const PopupMenuItem<MenuOptions>( value: MenuOptions.addToCache, child: Text('Add to cache'), ), const PopupMenuItem<MenuOptions>( value: MenuOptions.listCache, child: Text('List cache'), ), const PopupMenuItem<MenuOptions>( value: MenuOptions.clearCache, child: Text('Clear cache'), ), const PopupMenuItem<MenuOptions>( value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), ], ); }, ); } 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.evaluateJavascript( 'Toaster.postMessage("User Agent: " + navigator.userAgent);'); //JS调用Flutter } void _onListCookies( WebViewController controller, BuildContext context) async { final String cookies = await controller.evaluateJavascript('document.cookie'); //Flutter调用JS // ignore: deprecated_member_use Scaffold.of(context).showSnackBar(SnackBar( content: Column( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Cookies:'), _getCookieList(cookies), ], ), )); } void _onAddToCache(WebViewController controller, BuildContext context) async { await controller.evaluateJavascript( '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.'), )); } void _onListCache(WebViewController controller, BuildContext context) async { await controller.evaluateJavascript('caches.keys()' '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } void _onClearCache(WebViewController controller, BuildContext context) async { await controller.clearCache(); // ignore: deprecated_member_use Scaffold.of(context).showSnackBar(const SnackBar( content: Text("Cache cleared."), )); } 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), )); } void _onNavigationDelegateExample( WebViewController controller, BuildContext context) async { final String contentBase64 = base64Encode(const Utf8Encoder().convert(kNavigationExamplePage)); await controller.loadUrl('data:text/html;base64,$contentBase64'); } 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(), ); } } class NavigationControls extends StatelessWidget { const NavigationControls(this._webViewControllerFuture) : assert(_webViewControllerFuture != null); final Future<WebViewController> _webViewControllerFuture; @override Widget build(BuildContext context) { return FutureBuilder<WebViewController>( future: _webViewControllerFuture, builder: (BuildContext context, AsyncSnapshot<WebViewController> snapshot) { final bool webViewReady = snapshot.connectionState == ConnectionState.done; final WebViewController controller = snapshot.data; return Row( children: <Widget>[ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: !webViewReady ? null : () async { if (await controller.canGoBack()) { await controller.goBack(); } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( const SnackBar(content: Text("No back history item")), ); return; } }, ), IconButton( icon: const Icon(Icons.arrow_forward_ios), onPressed: !webViewReady ? null : () async { if (await controller.canGoForward()) { await controller.goForward(); } else { // ignore: deprecated_member_use Scaffold.of(context).showSnackBar( const SnackBar( content: Text("No forward history item")), ); return; } }, ), IconButton( icon: const Icon(Icons.replay), onPressed: !webViewReady ? null : () { controller.reload(); }, ), ], ); }, ); } }
Android权限
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_first"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
该文档,大量参考了其他文章,仅作自己学习使用,不做商业用途
参考文章:
https://juejin.cn/post/6844903811488677901
https://pub.flutter-io.cn/packages/webview_flutter
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。