1. webview_flutter : 官方维护的 WebView 插件,特性基于原生和 Flutter SDK 封装,继承 StatefulWidget,因此支持内嵌于 flutter Widget 树中.
适用于:单个静态文件,远程webdi地址。缺点:不支持本地服务,不支持加载一个完整本地的html项目,比如vue项目。
2. flutter_webview_plugin : flutter社区进行维护,易用程度目前高于webview_flutter 。特性是基于原生 WebView 封装的 Flutter 插件,将原生的一些基本使用 API 封装好提供给 Flutter 调用,因此并不能内嵌于 Flutter Widget 树中。
在界面的跳转时必须得先释放掉,返回后又要重新初始化。
适用于:支持本地服务,加载一个完整本地的html项目,比如vue项目。
3. flutter_inappwebview : 个人开发者维护。基于原生和 Flutter SDK 封装,优点是内置了一个本地服务,缺点,更新太慢,bug修复很慢。
结合 flutter_webview_plugin 插件最佳
mounted() {
/// 给flutter app调用。vue的方法不会暴露给app使用 需要把方法名暴露给window
window.forFlutterInit = this.forFlutterInit;
window.forFlutterRender = this.forFlutterRender;
},
methods: {
forFlutterInit(){
console.log("想做什么都行");
},
forFlutterRender(){
console.log("第二个方法,想做什么都行");
},
}
# pubspec.yaml
...
webview_flutter: 0.3.22+1
...
class TestWebView extends StatefulWidget {
final props;
TestWebView({
this.props,
});
@override
_TestWebViewState createState() => _TestWebViewState();
}
class _TestWebViewState extends State<TestWebView> {
WebViewController _controller;
_loadHtmlFromAssets() async {
String fileText = await rootBundle.loadString('assets/webview/index_test.html');
await _controller
.loadUrl(Uri.dataFromString(fileText, mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString());
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width,
// height: 500, // 去掉高度尝试
child: WebView(
initialUrl: 'https://www.ifredom.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
// _controller.complete(webViewController);
_controller = webViewController;
_loadHtmlFromAssets();
},
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
navigationDelegate: (NavigationRequest request) {
// 拦截请求
if (request.url.contains('youtube')) {
print('重新开一个 webview to $request}');
Navigator.of(context).push(new MaterialPageRoute(builder: (_) {
return TestWebView();
}));
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
onPageStarted: (String url) {
print('Page 开始 loading: $url');
},
onPageFinished: (String url) {
print('Page 结束 loading: $url');
// flutter 调用js方法
String script = "forFlutterInit('flutter传入的值')";
_controller.evaluateJavascript(script).then((result) {
print(result);
setState(() {
// ...
});
});
},
gestureNavigationEnabled: true,
),
);
}
}
test.html 用于测试,需要提前在pubspec.yaml中添加
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">
<link rel=icon href=favicon.ico>
<title>测试</title>
</head>
<body>
html内容页面
<button onclick="callFlutter()">点击</button>
<div id="p1">Flutter 调用了 JS.</div>
<script>
function forFlutterInit(message) {
// html页面获取flutter 传入参数
document.getElementById("p1").innerHTML = message;
}
function callFlutter() {
// html页面调用flutter 方法
Toaster.postMessage("html页面 调用Flutter方法");
}
</script>
</body>
</html>
# pubspec.yaml
...
flutter_webview_plugin: 0.3.11
...
第一步. localserver.dart 用于启动本地服务,在文尾。
class MusicScoreWebView extends StatefulWidget {
@override
_MusicScoreWebViewState createState() => _MusicScoreWebViewState();
}
class _MusicScoreWebViewState extends State<MusicScoreWebView> {
final flutterWebViewPlugin = FlutterWebviewPlugin();
CopyInAppLocalhostServer localhostServer = locator<CopyInAppLocalhostServer>();
double _viewProgress = 0;
String _url = "";
Future _initLocalServe() async {
print("启动 server/webview 服务");
await localhostServer.start();
await addListenerWebview(); // 如果不需要中途更改html内容,可比不添加此监听方法。监听WebView的加载事件
}
Future _dispose() async {
flutterWebViewPlugin.dispose(); //必须关闭
await localhostServer.close();
print("关闭 server/webview 服务");
}
Future<void> addListenerWebview({String name}) async {
double _viewProgress = 0;
flutterWebViewPlugin.onProgressChanged.listen((viewProgress) async {
print("加载进度 $viewProgress");
_viewProgress = viewProgress;
});
flutterWebViewPlugin.onStateChanged.listen((viewState) async {
print("状态改变了: ${viewState.type}");
if (viewState.type == WebViewState.finishLoad) {
print("onStateChanged 页面加载完成");
if (_viewProgress == 1.0) {
String _testData = "123456";
String _initScript = "forFlutterInit('$_testData')";
await flutterWebViewPlugin.evalJavascript(_initScript).then((result) async {
print('调用 forFlutterInit 方法返回值: $result');
});
}
}
});
}
@override
void initState() {
super.initState();
_initLocalServe();
}
@override
void dispose() {
_dispose()
super.dispose();
}
@override
Widget build(BuildContext context) {
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'FlutterToast',
onMessageReceived: (JavascriptMessage message) {
showToast(message.message);
},
),
].toSet();
return SizedBox(
width: MediaQuery.of(context).size.width,
// height: 500, // 去掉高度尝试
child: WebviewScaffold(
url: "http://localhost:8085/assets/index.html",
javascriptChannels: jsChannels,
mediaPlaybackRequiresUserGesture: false,
// appBar: AppBar(
// title: const Text('Widget WebView'),
// ),
withZoom: true,
withLocalStorage: true,
hidden: true,
initialChild: Container(
child: const Center(child: Text('正在加载中.....')),
),
key: Key("webview"),
debuggingEnabled: true,
),
);
}
}
此插件参数过多,并且需要配置andorid, 更多参数使用方法,详见官方文档
# pubspec.yaml
...
flutter_inappwebview 4.0.0+4
...
InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Widget _buildWebviewScaffold() {
return InAppWebView(
initialUrl: "http://localhost:8080/assets/index.html",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
inAppWebViewOptions: InAppWebViewOptions(
debuggingEnabled: true,
)
),
onWebViewCreated: (InAppWebViewController controller) {
print("做点什么");
},
onLoadStart: (InAppWebViewController controller, String url) {
print("做点什么");
},
onLoadStop: (InAppWebViewController controller, String url) {
print("做点什么");
},
);
}
CopyInAppLocalhostServer.start();
CopyInAppLocalhostServer.close();
// localserver.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart' show rootBundle;
import 'package:mime/mime.dart';
class CopyInAppLocalhostServer {
// final _log = Logger("CopyInAppLocalhostServer");
HttpServer _server;
int _port = 8080;
CopyInAppLocalhostServer({int port = 8080}) {
this._port = port;
}
Future<void> start() async {
if (this._server != null) {
throw Exception('Server already started on http://localhost:$_port');
}
var completer = Completer();
runZoned(() {
HttpServer.bind('127.0.0.1', _port).then((server) {
print('本地服务 http://localhost:' + _port.toString());
this._server = server;
server.listen((HttpRequest request) async {
var body = List<int>();
var path = request.requestedUri.path;
path = (path.startsWith('/')) ? path.substring(1) : path;
path += (path.endsWith('/')) ? 'index.html' : '';
try {
body = (await rootBundle.load(path)).buffer.asUint8List();
} catch (e) {
print(e.toString());
await request.response.close();
return;
}
var contentType = ['text', 'html'];
if (!request.requestedUri.path.endsWith('/') && request.requestedUri.pathSegments.isNotEmpty) {
var mimeType = lookupMimeType(request.requestedUri.path, headerBytes: body);
if (mimeType != null) {
contentType = mimeType.split('/');
}
}
request.response.headers.contentType = ContentType(contentType[0], contentType[1], charset: 'utf-8');
request.response.add(body);
await request.response.close();
});
completer.complete();
});
}, onError: (e, stackTrace) => print('Error: $e $stackTrace'));
return completer.future;
}
///Closes the server.
Future<void> close() async {
if (this._server != null) {
await this._server.close(force: true);
print('本地服务 on http://localhost:$_port closed');
this._server = null;
}
}
}
如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<