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关闭背景音
学习记录,每天不停进步。