Flutter内嵌网页插件flutter_iframe_webview的使用
Flutter内嵌网页插件flutter_iframe_webview的使用
功能
一个运行在Flutter Web
上的WebView
组件,它是基于 webview_flutter_web
插件修改的,实现了PlatformWebViewController
抽象类的所有方法,抹平了所有方法在全平台的调用异常,额外针对部分方法实现了相应的功能。
此插件主要针对如何在flutter web
与web
中通信交互提供了一种解决方案,因为该插件是基于iframe
模拟了一个webview
,受制于浏览器的同源策略,不同域之间通信是会出现跨域问题的,最好的解决方式是通过postMessage
去实现通信。此外,借助于 flutter_jsbridge_sdk
插件,我们制定了统一的规范在flutter web
与web
进行通信。
方法 | 是否实现 | 说明 |
---|---|---|
loadFile | 否 | 平台不支持 |
loadFlutterAsset | 否 | 平台不支持 |
loadHtmlString | 是 | |
loadRequest | 是 | |
currentUrl | 是 | |
canGoBack | 否 | 平台不支持 |
canGoForward | 否 | 平台不支持 |
goBack | 是 | |
reload | 是 | |
clearCache | 是 | |
clearLocalStorage | 是 | |
setPlatformNavigationDelegate | 是 | 部分实现(仅onPageFinished 方法) |
runJavaScript | 是 | |
runJavaScriptReturningResult | 是 | |
addJavaScriptChannel | 是 | 仅支持添加jsBridge.channelName |
removeJavaScriptChannel | 是 | |
getTitle | 是 | |
scrollTo | 是 | |
scrollBy | 是 | |
getScrollPosition | 是 | |
enableZoom | 否 | 平台不支持 |
setBackgroundColor | 是 | |
setJavaScriptMode | 是 | |
setUserAgent | 否 | 平台不支持 |
开始
在工程的pubspec.yaml
文件中添加flutter_jsbridge_sdk
插件
dependencies:
flutter_iframe_webview: ^1.0.2
flutter_jsbridge_sdk: ^1.0.1
导入相关的包:
import 'package:flutter_iframe_webview/webview_flutter_web.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';
Flutter配置
WebViewController
late final PlatformWebViewController platform;
if (WebViewPlatform.instance is WebWebViewPlatform) {
platform = WebWebViewController(WebWebViewControllerCreationParams());
} else {
platform = PlatformWebViewController(
const PlatformWebViewControllerCreationParams());
}
final WebViewController controller = WebViewController.fromPlatform(platform);
controller
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
),
)
..addJavaScriptChannel(
jsBridge.channelName,
onMessageReceived: (JavaScriptMessage message) {
jsBridge.onMessageReceived(message.message);
},
)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..loadRequest(Uri.parse('http://192.168.101.143:5500/page1.html'));
JavaScriptChannel
controller.addJavaScriptChannel(
jsBridge.channelName,
onMessageReceived: (JavaScriptMessage message) {
jsBridge.onMessageReceived(message.message);
},
);
jsBridge
jsBridge.init(
messageExecutor: (controller.platform as WebWebViewController).postMessage,
debug: true,
);
JS配置
为了正常通信,web
端还需引入javascript-jsbridge-sdk
库,
如何通信
关于如何在flutter_web
与web
中通信,请参考 flutter_jsbridge_sdk
完整示例demo
以下是一个完整的示例代码,展示了如何在Flutter应用中使用flutter_iframe_webview
插件来加载网页,并实现与Web端的通信。
import 'dart:convert';
import 'package:example/webview_controller_delegate/webview_controller_delegate.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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>
''';
const String kLocalExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.flutter-io.cn/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
''';
const String kTransparentBackgroundPage = '''
<!DOCTYPE html>
<html>
<head>
<title>Transparent background test</title>
</head>
<style type="text/css">
body { background: transparent; margin: 0; padding: 0; }
#container { position: relative; margin: 0; padding: 0; width: 100vw; height: 100vh; }
#shape { background: red; width: 200px; height: 200px; margin: 0; padding: 0; position: absolute; top: calc(50% - 100px); left: calc(50% - 100px); }
p { text-align: center; }
</style>
<body>
<div id="container">
<p>Transparent background test</p>
<div id="shape"></div>
</div>
</body>
</html>
''';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final WebViewControllerDelegate _controller;
[@override](/user/override)
void initState() {
super.initState();
final WebViewControllerDelegate controller = WebViewControllerDelegate()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
),
)
..addJavaScriptChannel(
jsBridge.channelName,
onMessageReceived: (JavaScriptMessage message) {
jsBridge.onMessageReceived(message.message);
},
)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (JavaScriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..loadRequest(Uri.parse('http://192.168.101.143:5500/page1.html'));
_controller = controller;
jsBridge.init(
messageExecutor: _controller.messageExecutor,
debug: true,
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('WebView'),
actions: <Widget>[NavigationControls(webViewController: _controller)],
),
body: Column(
children: <Widget>[
Expanded(child: WebViewWidget(controller: _controller)),
SampleMenu(webViewController: _controller),
],
),
);
}
Widget favoriteButton() {
return FloatingActionButton(
onPressed: () async {
final String? url = await _controller.currentUrl();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Favorited $url')),
);
}
},
child: const Icon(Icons.favorite),
);
}
}
class SampleMenu extends StatelessWidget {
SampleMenu({
super.key,
required this.webViewController,
});
final WebViewController webViewController;
late final WebViewCookieManager cookieManager = WebViewCookieManager();
[@override](/user/override)
Widget build(BuildContext context) {
return ColoredBox(
color: Colors.grey.withOpacity(0.3),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
TextButton(
onPressed: () {
_onShowUserAgent(context);
},
child: const Text('Show user agent'),
),
TextButton(
onPressed: () {
_onSetCookie();
},
child: const Text('Set cookie'),
),
TextButton(
onPressed: () {
_onListCookies(context);
},
child: const Text('List cookies'),
),
TextButton(
onPressed: () {
_onClearCookies(context);
},
child: const Text('Clear cookies'),
),
TextButton(
onPressed: () {
_onAddToCache(context);
},
child: const Text('Add to cache'),
),
TextButton(
onPressed: () {
_onListCache(context);
},
child: const Text('List cache'),
),
TextButton(
onPressed: () {
_onClearCache(context);
},
child: const Text('Clear Cache'),
),
TextButton(
onPressed: () {
_onNavigationDelegateExample();
},
child: const Text('Navigation Delegate example'),
),
TextButton(
onPressed: () {
_onDoPostRequest();
},
child: const Text('Post Request'),
),
TextButton(
onPressed: () {
_onLoadHtmlStringExample();
},
child: const Text('Load HTML string'),
),
TextButton(
onPressed: () {
_onLoadLocalFileExample();
},
child: const Text('Load local file'),
),
TextButton(
onPressed: () {
_onLoadFlutterAssetExample();
},
child: const Text('Load Flutter Asset'),
),
TextButton(
onPressed: () {
_onTransparentBackground();
},
child: const Text('Transparent background example'),
),
],
),
),
);
}
Future<void> _onShowUserAgent(BuildContext context) async {
final String userAgent = await webViewController
.runJavaScriptReturningResult('navigator.userAgent') as String;
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(userAgent)));
}
}
Future<void> _onListCookies(BuildContext context) async {
final String cookies = await webViewController
.runJavaScriptReturningResult('document.cookie') as String;
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Cookies:'),
_getCookieList(cookies),
],
),
));
}
}
Future<void> _onAddToCache(BuildContext context) async {
await webViewController.runJavaScript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";',
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Added a test entry to cache.'),
));
}
}
Future<void> _onListCache(BuildContext context) async {
final String caches = await webViewController.runJavaScriptReturningResult(
'caches.keys()'
// ignore: missing_whitespace_between_adjacent_strings
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
'.then((caches) => console.error(caches))') as String;
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(caches),
));
}
return webViewController.runJavaScript('caches.keys()'
// ignore: missing_whitespace_between_adjacent_strings
'.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
'.then((caches) => Toaster.postMessage(caches))');
}
Future<void> _onClearCache(BuildContext context) async {
await webViewController.clearCache();
await webViewController.clearLocalStorage();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Cache cleared.'),
));
}
}
Future<void> _onClearCookies(BuildContext context) async {
if (kIsWeb) {
webViewController.runJavaScript("document.cookie = ''");
return;
}
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(message),
));
}
}
Future<void> _onNavigationDelegateExample() {
final String contentBase64 = base64Encode(
const Utf8Encoder().convert(kNavigationExamplePage),
);
return webViewController.loadRequest(
Uri.parse('data:text/html;base64,$contentBase64'),
);
}
Future<void> _onSetCookie() async {
if (kIsWeb) {
await webViewController.runJavaScript(
"document.cookie = '${Uri.encodeComponent('foo')}=${Uri.encodeComponent('bar')}; path=/anything;'");
} else {
await cookieManager.setCookie(
const WebViewCookie(
name: 'foo',
value: 'bar',
domain: 'httpbin.org',
path: '/anything',
),
);
}
await webViewController.loadRequest(Uri.parse(
'https://httpbin.org/anything',
));
}
Future<void> _onDoPostRequest() {
return webViewController.loadRequest(
Uri.parse('https://httpbin.org/post'),
method: LoadRequestMethod.post,
headers: {'foo': 'bar', 'Content-Type': 'text/plain'},
body: Uint8List.fromList('Test Body'.codeUnits),
);
}
Future<void> _onLoadLocalFileExample() async {
final String pathToIndex = await _prepareLocalFile();
await webViewController.loadFile(pathToIndex);
}
Future<void> _onLoadFlutterAssetExample() {
return webViewController.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadHtmlStringExample() {
return webViewController.loadHtmlString(kLocalExamplePage);
}
Future<void> _onTransparentBackground() {
return webViewController.loadHtmlString(kTransparentBackgroundPage);
}
Widget _getCookieList(String cookies) {
if (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 {
return '';
// 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;
}
}
class NavigationControls extends StatelessWidget {
const NavigationControls({super.key, required this.webViewController});
final WebViewController webViewController;
[@override](/user/override)
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () async {
if (await webViewController.canGoBack()) {
await webViewController.goBack();
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No back history item')),
);
}
}
},
),
IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: () async {
if (await webViewController.canGoForward()) {
await webViewController.goForward();
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No forward history item')),
);
}
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: () => webViewController.reload(),
),
],
);
}
}
更多关于Flutter内嵌网页插件flutter_iframe_webview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter内嵌网页插件flutter_iframe_webview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用flutter_iframe_webview
插件来内嵌网页的示例代码。flutter_iframe_webview
插件允许你在Flutter应用中嵌入和显示网页内容。不过需要注意的是,flutter_iframe_webview
并不是Flutter社区中非常常见或广泛使用的插件,通常更常用的插件是webview_flutter
或flutter_inappwebview
。但为了满足你的要求,我将假设flutter_iframe_webview
插件存在并具有类似的API。
首先,你需要在你的pubspec.yaml
文件中添加flutter_iframe_webview
依赖(注意:这个包名是我假设的,实际使用时请替换为真实的包名):
dependencies:
flutter:
sdk: flutter
flutter_iframe_webview: ^x.y.z # 替换为实际的版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter应用中,你可以按照以下方式使用flutter_iframe_webview
:
import 'package:flutter/material.dart';
import 'package:flutter_iframe_webview/flutter_iframe_webview.dart'; // 假设这是正确的导入路径
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: WebViewExample(),
);
}
}
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
final WebViewController _controller = WebViewController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter WebView Example'),
),
body: Column(
children: <Widget>[
Expanded(
child: IFrameWebView(
initialUrl: 'https://www.example.com', // 你要加载的网页URL
controller: _controller,
onWebViewCreated: (WebViewController webViewController) {
// 可以在这里保存WebViewController的实例,用于后续的操作,比如加载新的URL
_controller = webViewController;
},
javascriptMode: JavascriptMode.unrestricted, // 是否允许执行JavaScript
),
),
ElevatedButton(
onPressed: () {
// 示例:通过控制器加载新的URL
_controller.loadUrl('https://www.google.com');
},
child: Text('Load Google'),
),
],
),
);
}
}
在这个示例中:
- 我们定义了一个
WebViewExample
的StatefulWidget,并在其State中创建了一个WebViewController
实例。 - 使用
IFrameWebView
组件来嵌入网页,并设置了初始URL、控制器以及网页创建时的回调。 - 提供了一个按钮,通过控制器加载新的URL。
请注意,这个示例是基于假设的flutter_iframe_webview
插件的API。如果实际插件的API不同,你可能需要调整代码。对于真实项目,建议使用社区广泛验证和支持的插件,如webview_flutter
或flutter_inappwebview
。