flutter开发实战-Webview及dispose关闭背景音
当在使用webview的时候,dispose需要关闭网页的背景音或者音效。
一、webview的使用
在工程的pubspec.yaml中引入插件
webview_flutter: ^4.4.2webview_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 preferredSizeSize get preferredSize => Size(ScreenUtil().screenWidth, toolbarHeight + ScreenUtil().statusBarHeight);@overrideState<StatefulWidget> createState() => _WebAppBarState();
}class _WebAppBarState extends State<WebAppBar> {@overrideWidget 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是为了增加elevationelevation: 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_importsclass 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;@overrideState<WebViewSkeleton> createState() => _WebViewSkeletonState();
}class _WebViewSkeletonState extends State<WebViewSkeleton> {// WebViewControllerlate final WebViewController _webController;// 尝试3次,每次间隔2秒int _loadTitleTimes = 0;bool _isDisposed = false;@overridevoid initState() {// TODO: implement initStatesuper.initState();_isDisposed = false;initWebController();}void initWebController() {// #docregion platform_featureslate 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_featurescontroller..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_featuresif (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) {// 返回当前urlprint("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);}@overridevoid dispose() {// TODO: implement disposeprint("_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();}@overrideWidget 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;@overrideState<WebViewPage> createState() => _WebViewPageState();
}class _WebViewPageState extends State<WebViewPage> {String title = "";String? url;// WebViewControllerWebViewController? _webViewController;double webProgress = 0.0;String? webViewUserAgent;String? appUserAgent;String? webTitle;@overridevoid initState() {// TODO: implement initStateif (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();}@overridevoid dispose() {// TODO: implement disposesuper.dispose();}@overrideWidget 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 onWebProgressdouble 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的时候,我们可以通过加载一个空白页面,来实现这个关闭背景音乐。
加载空白
代码如下
@overridevoid dispose() {// TODO: implement disposeprint("_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关闭背景音
学习记录,每天不停进步。