flutter开发实战-Webview及dispose关闭背景音

flutter开发实战-Webview及dispose关闭背景音

当在使用webview的时候,dispose需要关闭网页的背景音或者音效。

一、webview的使用

在工程的pubspec.yaml中引入插件

  webview_flutter: ^4.4.2
  webview_cookie_manager: ^2.0.6

Webview的使用代码如下

初始化WebViewController

controller = WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..setBackgroundColor(const Color(0x00000000))
  ..setNavigationDelegate(
    NavigationDelegate(
      onProgress: (int progress) {
        // Update loading bar.
      },
      onPageStarted: (String url) {},
      onPageFinished: (String url) {},
      onHttpError: (HttpResponseError error) {},
      onWebResourceError: (WebResourceError error) {},
      onNavigationRequest: (NavigationRequest request) {
        if (request.url.startsWith('https://www.youtube.com/')) {
          return NavigationDecision.prevent;
        }
        return NavigationDecision.navigate;
      },
    ),
  )
  ..loadRequest(Uri.parse('https://flutter.dev'));

将WebViewController传递给WebViewWidget

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Flutter Simple Example')),
    body: WebViewWidget(controller: controller),
  );
}

二、为了方便使用webview,进行封装成一个独立的widget

为了方便使用webview,进行封装成一个独立的widget

WebAppBar

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

/// 自定义Appbar
class WebAppBar extends StatefulWidget implements PreferredSizeWidget {
  const WebAppBar(
      {Key? key,
      required this.toolbarHeight,
      this.elevation,
      this.backgroundColor,
      this.leadingWidget,
      this.trailingWidget,
      this.centerWidget,
      this.brightness,
      this.backgroundImageName})
      : super(key: key);

  final double toolbarHeight;
  final double? elevation;
  final Color? backgroundColor;
  final Widget? leadingWidget;
  final Widget? trailingWidget;
  final Widget? centerWidget;
  final Brightness? brightness;
  final String? backgroundImageName;

  @override
  // TODO: implement preferredSize
  Size get preferredSize => Size(
      ScreenUtil().screenWidth, toolbarHeight + ScreenUtil().statusBarHeight);

  @override
  State<StatefulWidget> createState() => _WebAppBarState();
}

class _WebAppBarState extends State<WebAppBar> {
  @override
  Widget build(BuildContext context) {
    final SystemUiOverlayStyle overlayStyle =
        widget.brightness == Brightness.dark
            ? SystemUiOverlayStyle.light
            : SystemUiOverlayStyle.dark;

    Widget leadingWidget = (widget.leadingWidget ?? Container());
    Widget centerWidget = (widget.centerWidget ?? Container());
    Widget trailingWidget = (widget.trailingWidget ?? Container());

    return AnnotatedRegion<SystemUiOverlayStyle>(
      //套AnnotatedRegion是为了增加状态栏控制
      value: overlayStyle,
      child: Material(
        color: Colors.transparent,
        //套Material是为了增加elevation
        elevation: widget.elevation ?? 0,
        child: Container(
          padding: EdgeInsets.symmetric(horizontal: 0.0),
          height: widget.toolbarHeight + ScreenUtil().statusBarHeight,
          decoration: BoxDecoration(
            color: widget.backgroundColor,
          ),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Container(
                height: ScreenUtil().statusBarHeight,
              ),
              Expanded(
                child: Container(
                  height: widget.toolbarHeight,
                  alignment: Alignment.center,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Container(
                        height: widget.toolbarHeight,
                        child: leadingWidget,
                      ),
                      Expanded(
                        child: Container(
                          alignment: Alignment.center,
                          height: widget.toolbarHeight,
                          child: centerWidget,
                        ),
                      ),
                      Container(
                        height: widget.toolbarHeight,
                        child: trailingWidget,
                      ),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

使用webview的Widget

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

// #docregion platform_imports
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';

// Import for iOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
// #enddocregion platform_imports

class WebViewSkeleton extends StatefulWidget {
  const WebViewSkeleton({
    Key? key,
    required this.url,
    required this.onWebProgress,
    required this.onWebResourceError,
    required this.onLoadFinished,
    this.onWebTitleLoaded,
    required this.onWebViewCreated,
    this.appUserAgent,
    this.webViewUserAgent,
  }) : super(key: key);

  final String url;
  final String? appUserAgent;
  final String? webViewUserAgent;
  final Function(int progress) onWebProgress;
  final Function(WebResourceError error) onWebResourceError;
  final Function(String? url) onLoadFinished;
  final Function(String? webTitle)? onWebTitleLoaded;
  final Function(WebViewController controller) onWebViewCreated;

  @override
  State<WebViewSkeleton> createState() => _WebViewSkeletonState();
}

class _WebViewSkeletonState extends State<WebViewSkeleton> {
  // WebViewController
  late final WebViewController _webController;

  // 尝试3次,每次间隔2秒
  int _loadTitleTimes = 0;

  bool _isDisposed = false;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _isDisposed = false;
    initWebController();
  }

  void initWebController() {
    // #docregion platform_features
    late final PlatformWebViewControllerCreationParams params;
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }

    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(params);
    // #enddocregion platform_features

    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setUserAgent(widget.webViewUserAgent)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('WebView is loading (progress : $progress%)');
            widget.onWebProgress(progress);
          },
          onPageStarted: (String url) {
            debugPrint('Page started loading: $url');
            // 网页开始加载
            webPageLoadedStart();
            print('onPageStarted url: $url');
          },
          onPageFinished: (String url) {
            debugPrint('Page finished loading: $url');
            // 网页加载完成
            print('onPageFinished url: $url');

            // 加载完成
            widget.onLoadFinished(url);

            // 获取网页的标题
            getWebPageTitle(url: url);
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.isForMainFrame}
          ''');
            print("onWebResourceError:${error}");
            widget.onWebResourceError(error);
          },
          onNavigationRequest: (NavigationRequest request) {
            String url = Uri.decodeComponent(request.url);
            bool canNavigate = false;
            if (url.startsWith("http")) {
              canNavigate = true;
            }
            // 允许路由替换
            return canNavigate
                ? NavigationDecision.navigate
                : NavigationDecision.prevent;
          },
          onUrlChange: (UrlChange change) {
            debugPrint('url change to ${change.url}');
          },
          // onHttpAuthRequest: (HttpAuthRequest request) {
          //   openDialog(request);
          // },
        ),
      );

    // #docregion platform_features
    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController)
          .setMediaPlaybackRequiresUserGesture(false);
    }
    // #enddocregion platform_features

    _webController = controller;
    onWebViewCreated();
  }

  void onWebViewCreated() {
    print("onWebViewCreated");
    // controller.loadUrl(url);此时也可以初始化一个url
    _webController.canGoBack().then((res) {
      // 是否能返回上一级
      print("controller.canGoBack res: $res");
    });
    _webController.currentUrl().then((url) {
      // 返回当前url
      print("controller.currentUrl url: $url");
    });
    _webController.canGoForward().then((res) {
      //是否能前进
      print("controller.canGoForward res: $res");
    });

    String filePre = "file://";
    if (widget.url.startsWith(filePre)) {
      String html = widget.url.substring(filePre.length);
      DefaultAssetBundle.of(context)
          .loadString('assets/htmls/${html}')
          .then((value) => _webController?.loadHtmlString(value));
    } else {
      if (widget.url.startsWith("http://") ||
          widget.url.startsWith("https://")) {
        _webController.loadRequest(Uri.parse(widget.url), headers: {
          'Referer': widget.url,
        });
      }
    }

    widget.onWebViewCreated(_webController);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    print("_WebViewSkeletonState dispose");
    _isDisposed = true;
    webControllerDispose();
    super.dispose();
  }

  Future<void> webControllerDispose() async {
    /// dispose打开空白页面,关闭音频
    String url = "about:blank";
    await _webController?.loadRequest(Uri.parse(url), headers: {

    });
    _webController?.clearCache();
    _webController?.clearLocalStorage();
  }

  void webPageLoadedStart() {
    _loadTitleTimes = 0;
  }

  Future<void> getWebPageTitle({required String url}) async {
    if (_isDisposed) {
      return;
    }

    String? title = await _webController?.getTitle();
    print("getWebPageTitle:${title}");
    if (title != null && title.isNotEmpty) {
      print("webTitle a:${title}");
      setWebPageTitle(title);
    } else {
      try {
        var result = await _webController
            ?.runJavaScriptReturningResult('window.document.title');
        print("webTitle document.url:${result}");
        if (result != null && (result is String) && result.isNotEmpty) {
          setWebPageTitle(result);
        } else {
          result = await _webController?.runJavaScriptReturningResult(
              'window.document.getElementsByTagName("title")[0]');
          print("webTitle document.getElementsByTagName:${result}");
          setWebPageTitle(result);
        }
      } catch (e) {
        print("getWebPageTitle:${e.toString()}");
        // 最多尝试三次
        if (_loadTitleTimes < 3) {
          Future.delayed(Duration(seconds: 2), () {
            _loadTitleTimes++;
            getWebPageTitle(url: url);
          });
        }
      }
    }
  }

  // 设置页面标题
  void setWebPageTitle(data) {
    if (widget.onWebTitleLoaded != null) {
      widget.onWebTitleLoaded!(data);
    }
  }

  // 返回
  void goBack() {
    _webController?.canGoBack().then((res) {
      // 是否能返回上一级
      print("controller.canGoBack res: $res");
      if (true == res) {
        _webController?.goBack();
      }
    });
  }

  // 刷新
  void reload() {
    _webController?.reload();
  }

  @override
  Widget build(BuildContext context) {
    return buildWebView(context);
  }

  Widget buildWebView(BuildContext context) {
    return WebViewWidget(
      controller: _webController,
    );
  }
}

使用web view的页面webviewPage

class WebViewPage extends StatefulWidget {
  const WebViewPage({
    Key? key,
    this.arguments,
  }) : super(key: key);

  final Object? arguments;

  @override
  State<WebViewPage> createState() => _WebViewPageState();
}

class _WebViewPageState extends State<WebViewPage> {
  String title = "";
  String? url;

  // WebViewController
  WebViewController? _webViewController;

  double webProgress = 0.0;

  String? webViewUserAgent;
  String? appUserAgent;
  String? webTitle;

  @override
  void initState() {
    // TODO: implement initState
    if (widget.arguments != null && widget.arguments is Map) {
      Map obj = widget.arguments as Map;
      url = obj["url"];
      webViewUserAgent = obj['webViewUserAgent'];
      appUserAgent = obj['appUserAgent'];
      webTitle = obj['webTitle'];
    }

    loggerInfo("_WebViewPageState arguments:${widget.arguments}");

    loggerInfo("_WebViewPageState url:${url}");

    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: WebAppBar(
          toolbarHeight: 44.0,
          backgroundColor: Theme.of(context).primaryColor,
          centerWidget: Text(
            webTitle ?? title,
            textAlign: TextAlign.center,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              fontSize: 17,
              color: ColorUtil.hexColor(0xffffff),
              fontWeight: FontWeight.w600,
              fontStyle: FontStyle.normal,
              decoration: TextDecoration.none,
            ),
          ),
          leadingWidget: Row(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              IconButton(
                padding: EdgeInsets.all(0.0),
                onPressed: () {
                  webViewGoBack(context);
                },
                icon: Icon(
                  Icons.arrow_back_ios,
                  color: Colors.white,
                  size: 24.0,
                ),
              ),
              IconButton(
                padding: EdgeInsets.all(0.0),
                onPressed: () {
                  navigatorBack(context);
                },
                icon: Icon(
                  Icons.close_rounded,
                  color: Colors.white,
                  size: 30.0,
                ),
              ),
            ],
          ),
          trailingWidget: Row(
            mainAxisAlignment: MainAxisAlignment.end,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              SizedBox(
                width: 28.0,
              ),
              IconButton(
                padding: EdgeInsets.all(0.0),
                onPressed: () {
                  webViewReload();
                },
                icon: Icon(
                  Icons.refresh_outlined,
                  color: Colors.white,
                  size: 28.0,
                ),
              ),
            ],
          ),
        ),
        body: Stack(
          children: [
            WebViewSkeleton(
              url: url ?? "",
              onWebResourceError: (WebResourceError error) {
                if (mounted) {
                  // TODO onWebResourceError
                }
              },
              onWebProgress: (int progress) {
                if (mounted) {
                  // TODO onWebProgress
                  double precent = progress / 100.0;
                  if (precent > 1.0) {
                    precent = 1.0;
                  }

                  if (precent < 0.0) {
                    precent = 0.0;
                  }

                  setState(() {
                    webProgress = precent;
                    loggerInfo("webProgress:${webProgress}");
                  });
                }
              },
              onLoadFinished: (String? url) {
                if (mounted) {
                  // TODO onLoadFinished
                }
              },
              onWebTitleLoaded: (String? webTitle) {
                if (mounted) {
                  String? aWebTitle;
                  if ('""' != webTitle) {
                    aWebTitle = webTitle;
                  }
                  setState(() {
                    title = aWebTitle ?? "";
                  });
                }
              },
              onWebViewCreated: (WebViewController controller) {
                _webViewController = controller;
              },
            ),
            buildProgressIndicator(context),
          ],
        ),
    );
  }

  Widget buildProgressIndicator(BuildContext context) {
    return (webProgress != 1.0)
        ? LinearProgressIndicator(
            backgroundColor: Colors.transparent,
            valueColor: AlwaysStoppedAnimation(ColorUtil.hexColor(0x3b93ff)),
            value: webProgress,
            minHeight: 2,
          )
        : Container();
  }

  void navigatorBack(BuildContext context) {
    Navigator.of(context).pop();
  }

  void webViewGoBack(BuildContext context) {
    _webViewController?.canGoBack().then((res) {
      // 是否能返回上一级
      loggerInfo("controller.canGoBack res: $res");
      if (true == res) {
        _webViewController?.goBack();
      } else {
        navigatorBack(context);
      }
    });
  }

  void webViewReload() {
    _webViewController?.reload();
  }
}

三、解决dispose关闭背景音乐

解决dispose关闭背景音乐的问题,当widget被dispose的时候,我们可以通过加载一个空白页面,来实现这个关闭背景音乐。

加载空白

代码如下

@override
  void dispose() {
    // TODO: implement dispose
    print("_WebViewSkeletonState dispose");
    _isDisposed = true;
    webControllerDispose();
    super.dispose();
  }

  Future<void> webControllerDispose() async {
    /// dispose打开空白页面,关闭音频
    String url = "about:blank";
    await _webController?.loadRequest(Uri.parse(url), headers: {

    });
    _webController?.clearCache();
    _webController?.clearLocalStorage();
  }

四、小结

flutter开发实战-Webview及dispose关闭背景音

学习记录,每天不停进步。

相关推荐
AiFlutter1 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
m0_748247801 天前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
迷雾漫步者1 天前
Flutter组件————PageView
flutter·跨平台·dart
希忘auto1 天前
详解Redis的常用命令
redis·1024程序员节
迷雾漫步者1 天前
Flutter组件————FloatingActionButton
前端·flutter·dart
coder_pig2 天前
📝小记:Ubuntu 部署 Jenkins 打包 Flutter APK
flutter·ubuntu·jenkins
捡芝麻丢西瓜2 天前
flutter自学笔记5- dart 编码规范
flutter·dart
恋猫de小郭2 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
yaosheng_VALVE2 天前
探究全金属硬密封蝶阀的奥秘-耀圣控制
运维·eclipse·自动化·pyqt·1024程序员节
dami_king2 天前
SSH特性|组成|SSH是什么?
运维·ssh·1024程序员节