通过后退按钮设备在 webview 中回退
Posted
技术标签:
【中文标题】通过后退按钮设备在 webview 中回退【英文标题】:goback in webview flutter by back button device 【发布时间】:2020-05-27 07:29:03 【问题描述】:我有 4 个底部导航栏,每个都有一个 web 视图。 但是当点击一个bottomNavigationBar并选择webview页面中的一个部分时,我无法通过按钮返回设备返回到webview中的上一页。
我想通过返回按钮设备返回 webview。
现在是按下返回按钮退出应用程序的时候。 但我使用 double_back_to_close_app.dart 退出应用程序。
家庭班:
import 'package:webview_flutter/webview_flutter.dart';
import 'package:fancy_bar/fancy_bar.dart';
import 'placeholder_widget.dart';
class Home extends StatefulWidget
@override
_HomeState createState() => _HomeState();
class _HomeState extends State<Home>
int _currentIndex = 0;
final List<Widget> _children = [
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com'),
MyPlaceholderWidget('https://googel.com')
];
@override
Widget build(BuildContext context)
return Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.grey[200],
elevation: 0.0,
)
),
body: DoubleBackToCloseApp(
child: _children[_currentIndex],
snackBar: const SnackBar(
content: Text(
'برای خروج دو بار کلیک کنید',
style: TextStyle(fontFamily: "Shabnam"),
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
),
),
),
bottomNavigationBar: FancyBottomBar(
onItemSelected: onTabTapped,
selectedIndex: _currentIndex, // this will be set when a new tab is tapped
items: [
FancyItem(
textColor: Colors.green,
title: 'حساب',
icon: Icon(
LineIcons.user,
size: 29,
),
),
FancyItem(
textColor: Colors.green,
title: 'وبلاگ',
icon: Icon(
LineIcons.file_text,
size: 27,
),
),
FancyItem(
textColor: Colors.green,
title: 'سبد خرید',
icon: Icon(
LineIcons.shopping_cart,
size: 31,
),
),
FancyItem(
textColor: Colors.green,
title: 'صفحه اصلی',
icon: Icon(
LineIcons.home,
size: 28,
),
),
],
),
);
void onTabTapped(int index)
setState(()
_currentIndex = index;
);
和 placeholder_widget.dart 中的 webview 类:
WebViewController controller;
class MyPlaceholderWidget extends StatelessWidget
var url = 'https://www.mayehtaj.ir' ;
final key = UniqueKey();
MyPlaceholderWidget(String url)
this.url = url ;
@override
Widget build(BuildContext context)
return WebView(
key: key,
javascriptMode: JavascriptMode.unrestricted,
initialUrl: url,
onWebViewCreated: (WebViewController webViewController)
controller = webViewController;
);
如何检测按下后退按钮的设备并在一个网页视图中返回上一页?
谢谢你的帮助!
【问题讨论】:
【参考方案1】:您可以在下面复制粘贴运行完整代码
你可以用WillPopScope
包裹Scaffold
当用户点击device back button
时,可以执行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();
else
Scaffold.of(context).showSnackBar(
const SnackBar(content: Text("No back history item")),
);
return Future.value(false);
工作演示
完整代码
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();
,
),
],
);
,
);
【讨论】:
以上是关于通过后退按钮设备在 webview 中回退的主要内容,如果未能解决你的问题,请参考以下文章
ionic5 webview 应用程序浏览器事件与 android 设备后退按钮不同步