使用flutter webview作为主页并按下后退按钮关闭应用程序
Posted
技术标签:
【中文标题】使用flutter webview作为主页并按下后退按钮关闭应用程序【英文标题】:Using flutter webview as home and pressing back button closes Application 【发布时间】:2020-01-12 16:32:12 【问题描述】:我正在尝试在 Flutter 中使用 webview 作为我的应用程序的主页。一切都加载得很好,但是点击后退按钮不会将我带到 web 视图中的上一个网页,它只是退出应用程序。
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;
,
),
);
`
【问题讨论】:
我认为这是使用 webview 插件时的预期行为,除非您知道任何文档另有说明。 你试过使用 WebViewController 类吗?使用 canGoBack 方法检查是否有返回历史记录,然后使用 goBack 方法返回上一页。 @FarhanSyah 好主意,我会查看 webview 控制器,看看是否存在返回方法,谢谢 【参考方案1】: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;
,
);
,),
),
);
【讨论】:
_controller 来自哪里?是的,它不存在。删除这个 "返回 Future.value(false);"应该在 if/else 语句之外【参考方案2】:完美运行的代码
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);
【讨论】:
虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。【参考方案3】:您必须控制 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,
)),
),
);
【讨论】:
【参考方案4】:这是最好的答案(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(......)))
【讨论】:
【参考方案5】:用 WillPopScope 包裹 Scaffold 并在用户单击设备返回按钮时执行 WebView Controller goback
code sn-p 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),
],
),
退出应用的代码 sn-p
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”。并返回上一页
【讨论】:
pub.dev/packages/minimize_app 调用比Scaffold.of(context).showSnackBar(const SnackBar(content: Text("No back history item")),);
有用
@Yash 不是更好的答案。不使用 inappwebview
我想隐藏 appBar: AppBar.如果我删除 appBar - 后退导航不起作用。我该怎么办?
_exitApp()
的返回值是不是顺序不对?如果应用程序应该返回而不是 webview,它不应该返回 true 吗?
这项工作!非常感谢!以上是关于使用flutter webview作为主页并按下后退按钮关闭应用程序的主要内容,如果未能解决你的问题,请参考以下文章
使用 Agora SDK 进行视频通话时的 Flutter 通知