通过后退按钮设备在 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 中回退的主要内容,如果未能解决你的问题,请参考以下文章

按下设备后退按钮上的 Flutter WebView 异常

ionic5 webview 应用程序浏览器事件与 android 设备后退按钮不同步

在 webview 片段中实现后退按钮

如何使用智能手机后退按钮作为 webview 后退按钮?

如何在nativescript vue中处理webview上的android后退按钮

如果在 WebView 中按下后退按钮,如何返回上一页?