我正在尝试使用 flutter 中的 webview 作为我的应用程序的主页。一切都加载得很好,但是点击后退按钮不会将我带到网络视图中的上一个网页,它只是退出应用程序。
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class _StudentPortalState extends State<StudentPortal>{
_launchURL(url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
Widget build (BuildContext context){
return Scaffold(
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: 'https://sites.google.com/ttmsa.org/ttmsa/home',
navigationDelegate: (NavigationRequest request) {
if(request.url.contains("intent:")) {
_launchURL('https://forms.gle/XfNbn1Ph9xFdUaKeA');
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
);
}
}`
使用 WillPopScope 包裹脚手架,当用户单击设备后退按钮时执行 WebView 控制器返回
onwillpop 代码片段
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: 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),
],
),
退出应用程序的代码片段
WebViewController controllerGlobal;
Future<bool> _exitApp(BuildContext context) async {
if (await controllerGlobal.canGoBack()) {
print("onwill goback");
controllerGlobal.goBack();
return Future.value(true);
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
}
}
完整代码
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
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.com/">https://www.google.com/</a></ul>
<ul><a href="https://www.google.com/">https://nodejs.org/en</a></ul>
</ul>
</body>
</html>
''';
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
WebViewController controllerGlobal;
Future<bool> _exitApp(BuildContext context) async {
if (await controllerGlobal.canGoBack()) {
print("onwill goback");
controllerGlobal.goBack();
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
}
}
class _WebViewExampleState extends State<WebViewExample> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _exitApp(context),
child: 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://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
// ignore: prefer_collection_literals
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
if (request.url.startsWith('https://flutter.dev/docs')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
onPageFinished: (String url) {
print('Page finished loading: $url');
},
);
}),
floatingActionButton: favoriteButton(),
),
);
}
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
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();
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.
controller.evaluateJavascript(
'Toaster.postMessage("User Agent: " + navigator.userAgent);');
}
void _onListCookies(
WebViewController controller, BuildContext context) async {
final String cookies =
await controller.evaluateJavascript('document.cookie');
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";');
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();
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.';
}
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(message),
));
}
void _onNavigationDelegateExample(
WebViewController controller, BuildContext context) async {
final String contentBase64 =
base64Encode(const Utf8Encoder().convert(kNavigationExamplePage));
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;
controllerGlobal = controller;
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: !webViewReady
? null
: () async {
if (await controller.canGoBack()) {
controller.goBack();
} else {
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()) {
controller.goForward();
} else {
Scaffold.of(context).showSnackBar(
const SnackBar(
content: Text("No forward history item")),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: !webViewReady
? null
: () {
controller.reload();
},
),
],
);
},
);
}
}
工作演示。我点击桌面页面,然后按设备后退按钮,您可以看到信息显示“onwill goback”。然后返回上一页
完美运行的代码
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
class Homepage extends StatefulWidget {
@override
HomepageState createState() => HomepageState();
}
class HomepageState extends State<Homepage> {
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
),
);
}
Future<bool> _goBack(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
FlatButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
return Future.value(true);
}
}
}
你必须控制WillPopScope组件中的逻辑:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home', url: 'https://flutter.dev/'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.url});
final String title;
final String url;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onWillPop(context),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: WebView(
key: UniqueKey(),
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
initialUrl: widget.url,
)),
),
);
}
}
将
WillPopScope
包裹在 Webview 小部件上,并使用 canGoBack()
方法检查是否可以返回网页,如果可以,请使用 Webview 控制器中的 goBack()
方法转到上一个网页。
class TheScreen extends StatelessWidget{
WebViewController? _controller;
Widget build(BuildContext context){
return WillPopScope(
onWillPop: () async {
bool? goBack = _controller?.canGoBack();
if(goBack != true) return true;
else {
_controller.goBack();
return false;
}
},
child: WebView(
initialUrl: "www.flutter.com",
onWebViewCreated: (controller)=> _controller = controller),
}
}
}
这是必须更好的答案(100%工作)-
// Use any webview plugin
// WebView _controller;
Future<void> _handleBack(context) async {
var status = await _controller.canGoBack();
if (status) {
_controller.goBack();
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit'),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
FlatButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
}
}
这里正在调用WillPopScope
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _handleBack(context),
child: SafeArea(
top: true,
child: Scaffold(......)))
我改变后,chunhunghan的答案对我有用
return Future.value(true);
在 _exitApp() 的 if 块中
return Future.value(false);
如果为 true,网络视图将返回到上一个站点,在此之后 “默认设备后退按钮处理程序”(我不知道它的确切命名方式)关闭了应用程序。但是如果设置为 false,则打开 goBack 站点后应用程序不会关闭。
我将此作为额外答案发布,因为我的声誉太低,无法对答案发表评论。但我花了几个小时来找出这一点,并希望这些信息对其他人和我一样有帮助。
onbackpressed Webview 代码
处理后退按钮的未来
WebViewController controllerGlobal;
Future<bool> browserBack(BuildContext context) async{
print('activated');
if (await controllerGlobal.canGoBack()) {
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("Munching....")),
);
print("onwill goback");
controllerGlobal.goBack();
}
else {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
return Future.value(false);
}
}
WillPopScope 小部件
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()=> browserBack(context),
child:Scaffold(
body: Builder(builder: (BuildContext context){
return WebView(
initialUrl: 'https://flutter.dev/docs/deployment/android',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
// _controller.complete(webViewController);
controllerGlobal = webViewController;
},
);
},),
),
);
我发现了一种非常酷的前进和后退方式,既简单又实用。
这将由 appbar 完成。
appBar: AppBar(
backgroundColor: Colors.red[900],
actions: <Widget>[
ElevatedButton(onPressed: (){
print("Voltando");
controller.goBack();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
child: Image.network('https://raw.githubusercontent.com/HeroRickyGAMES/Projeto-CPJ-Consulta-Juridica-de-Processos/master/src/voltar.png')
),
ElevatedButton(onPressed: (){
print("Indo pra frente");
controller.goForward();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[900],
),
child: Image.network('https://raw.githubusercontent.com/HeroRickyGAMES/Projeto-CPJ-Consulta-Juridica-de-Processos/master/src/irprafrente.png')
)
],
title: Text("Minha Pagina Web"),
),
'WillPopScope'
已弃用,不应使用。请改用 PopScope。此功能在 v3.12.0-1.0.pre 之后已弃用。 (文档)
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
onPopScopePressBackShowAlertDialog();
},
child: Scaffold()
);
}
exit(0);
离开申请
Future onPopScopePressBackShowAlertDialog() async {
if (await webViewController.canGoBack()) {
webViewController.goBack();
} else {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Are you sure you want to close the application?',
textAlign: TextAlign.center,
),
contentPadding:
const EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
actions: <Widget>[
TextButton(
child: Text('Leave'),
onPressed: () {
exit(0);
},
),
ElevatedButton(
style: ElevatedButtonNormal(context),
child: Text('Stay'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
});
}
}