webview_flutter仓库地址:webview_flutter | Flutter package
github地址:https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter
要打开非https协议的网页,需要在安卓平台上添加权限:android:usesCleartextTraffic="true"
打开网页demo:
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.// ignore_for_file: public_member_api_docsimport 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
// #docregion platform_imports
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
// #enddocregion platform_importsvoid main() => runApp(const 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.com/">https://www.google.com/</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 HTMLstring using the <a href="https://pub.flutter-io.cn/packages/webview_flutter">Flutterwebview</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>
''';const String kLogExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body onload="console.log('Logging that the page is loading.')"><h1>Local demo page</h1>
<p>This page is used to test the forwarding of console logs to Dart.
</p><style>.btn-group button {padding: 24px; 24px;display: block;width: 25%;margin: 5px 0px 0px 0px;}
</style><div class="btn-group"><button onclick="console.error('This is an error message.')">Error</button><button onclick="console.warn('This is a warning message.')">Warning</button><button onclick="console.info('This is a info message.')">Info</button><button onclick="console.debug('This is a debug message.')">Debug</button><button onclick="console.log('This is a log message.')">Log</button>
</div></body>
</html>
''';class WebViewExample extends StatefulWidget {const WebViewExample({super.key});@overrideState<WebViewExample> createState() => _WebViewExampleState();
}class _WebViewExampleState extends State<WebViewExample> {late final WebViewController _controller;@overridevoid initState() {super.initState();// #docregion platform_featureslate final PlatformWebViewControllerCreationParams params;if (WebViewPlatform.instance is WebKitWebViewPlatform) {params = WebKitWebViewControllerCreationParams(allowsInlineMediaPlayback: true,mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},);} else {params = const PlatformWebViewControllerCreationParams();}final WebViewController controller =WebViewController.fromPlatformCreationParams(params);// #enddocregion platform_featurescontroller..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;},onUrlChange: (UrlChange change) {debugPrint('url change to ${change.url}');},onHttpAuthRequest: (HttpAuthRequest request) {openDialog(request);},),)..addJavaScriptChannel('Toaster',onMessageReceived: (JavaScriptMessage message) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message.message)),);},)..loadRequest(Uri.parse('http://192.168.1.171:5173/#/pad?team=red'));// #docregion platform_featuresif (controller.platform is AndroidWebViewController) {AndroidWebViewController.enableDebugging(true);(controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false);}// #enddocregion platform_features_controller = controller;}@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: Colors.green,body: WebViewWidget(controller: _controller),// floatingActionButton: favoriteButton(),);}Widget favoriteButton() {return FloatingActionButton(onPressed: () async {final String? url = await _controller.currentUrl();if (mounted) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Favorited $url')),);}},child: const Icon(Icons.favorite),);}Future<void> openDialog(HttpAuthRequest httpRequest) async {final TextEditingController usernameTextController =TextEditingController();final TextEditingController passwordTextController =TextEditingController();return showDialog(context: context,barrierDismissible: false,builder: (BuildContext context) {return AlertDialog(title: Text('${httpRequest.host}: ${httpRequest.realm ?? '-'}'),content: SingleChildScrollView(child: Column(mainAxisSize: MainAxisSize.min,children: <Widget>[TextField(decoration: const InputDecoration(labelText: 'Username'),autofocus: true,controller: usernameTextController,),TextField(decoration: const InputDecoration(labelText: 'Password'),controller: passwordTextController,),],),),actions: <Widget>[// Explicitly cancel the request on iOS as the OS does not emit new// requests when a previous request is pending.TextButton(onPressed: () {httpRequest.onCancel();Navigator.of(context).pop();},child: const Text('Cancel'),),TextButton(onPressed: () {httpRequest.onProceed(WebViewCredential(user: usernameTextController.text,password: passwordTextController.text,),);Navigator.of(context).pop();},child: const Text('Authenticate'),),],);},);}
}enum MenuOptions {showUserAgent,listCookies,clearCookies,addToCache,listCache,clearCache,navigationDelegate,doPostRequest,loadLocalFile,loadFlutterAsset,loadHtmlString,transparentBackground,setCookie,logExample,basicAuthentication,
}class SampleMenu extends StatelessWidget {SampleMenu({super.key,required this.webViewController,});final WebViewController webViewController;late final WebViewCookieManager cookieManager = WebViewCookieManager();@overrideWidget build(BuildContext context) {return PopupMenuButton<MenuOptions>(key: const ValueKey<String>('ShowPopupMenu'),onSelected: (MenuOptions value) {switch (value) {case MenuOptions.showUserAgent:_onShowUserAgent();case MenuOptions.listCookies:_onListCookies(context);case MenuOptions.clearCookies:_onClearCookies(context);case MenuOptions.addToCache:_onAddToCache(context);case MenuOptions.listCache:_onListCache();case MenuOptions.clearCache:_onClearCache(context);case MenuOptions.navigationDelegate:_onNavigationDelegateExample();case MenuOptions.doPostRequest:_onDoPostRequest();case MenuOptions.loadFlutterAsset:_onLoadFlutterAssetExample();case MenuOptions.loadHtmlString:_onLoadHtmlStringExample();case MenuOptions.transparentBackground:_onTransparentBackground();case MenuOptions.setCookie:_onSetCookie();case MenuOptions.logExample:_onLogExample();case MenuOptions.basicAuthentication:_promptForUrl(context);case MenuOptions.loadLocalFile:// TODO: Handle this case.}},itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[const PopupMenuItem<MenuOptions>(value: MenuOptions.showUserAgent,child: Text('Show user agent'),),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'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.doPostRequest,child: Text('Post Request'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.loadHtmlString,child: Text('Load HTML string'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.loadLocalFile,child: Text('Load local file'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.loadFlutterAsset,child: Text('Load Flutter Asset'),),const PopupMenuItem<MenuOptions>(key: ValueKey<String>('ShowTransparentBackgroundExample'),value: MenuOptions.transparentBackground,child: Text('Transparent background example'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.setCookie,child: Text('Set cookie'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.logExample,child: Text('Log example'),),const PopupMenuItem<MenuOptions>(value: MenuOptions.basicAuthentication,child: Text('Basic Authentication Example'),),],);}Future<void> _onShowUserAgent() {// Send a message with the user agent string to the Toaster JavaScript channel we registered// with the WebView.return webViewController.runJavaScript('Toaster.postMessage("User Agent: " + navigator.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() {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 {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 {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: <String, String>{'foo': 'bar', 'Content-Type': 'text/plain'},body: Uint8List.fromList('Test Body'.codeUnits),);}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(),);}Future<void> _onLogExample() {webViewController.setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) {debugPrint('== JS == ${consoleMessage.level.name}: ${consoleMessage.message}');});return webViewController.loadHtmlString(kLogExamplePage);}Future<void> _promptForUrl(BuildContext context) {final TextEditingController urlTextController = TextEditingController();return showDialog<String>(context: context,builder: (BuildContext context) {return AlertDialog(title: const Text('Input URL to visit'),content: TextField(decoration: const InputDecoration(labelText: 'URL'),autofocus: true,controller: urlTextController,),actions: <Widget>[TextButton(onPressed: () {if (urlTextController.text.isNotEmpty) {final Uri? uri = Uri.tryParse(urlTextController.text);if (uri != null && uri.scheme.isNotEmpty) {webViewController.loadRequest(uri);Navigator.pop(context);}}},child: const Text('Visit'),),],);},);}
}class NavigationControls extends StatelessWidget {const NavigationControls({super.key, required this.webViewController});final WebViewController webViewController;@overrideWidget 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(),),],);}
}
最后的效果图: