webview_page.dart 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import 'package:flutter/material.dart';
  2. import 'package:shared/utils/log_utils.dart';
  3. import 'package:url_launcher/url_launcher.dart';
  4. import 'dart:io';
  5. import 'package:webview_flutter/webview_flutter.dart';
  6. import 'package:widgets/my_appbar.dart';
  7. import 'package:cs_resources/constants/color_constants.dart';
  8. /// webview 封装
  9. // ignore: must_be_immutable
  10. class WebViewPage extends StatefulWidget {
  11. final String? initialUrl;
  12. bool? showAppbar = true;
  13. Map? arguments = {'title': '', 'initialUrl': ''};
  14. List<Widget>? actions = [];
  15. WebViewPage({Key? key, this.showAppbar, this.initialUrl, this.arguments, this.actions}) : super(key: key);
  16. @override
  17. _WebViewPageState createState() => _WebViewPageState();
  18. }
  19. class _WebViewPageState extends State<WebViewPage> {
  20. WebViewController? webViewController;
  21. final _key = UniqueKey();
  22. String title = '';
  23. bool _showAppbar = true;
  24. int _stackToView = 1;
  25. String? _initialUrl;
  26. double _webViewHeight = 200;
  27. @override
  28. void initState() {
  29. super.initState();
  30. title = widget.arguments != null ? widget.arguments!['title'] : null;
  31. _showAppbar = widget.showAppbar ?? true;
  32. _initialUrl = widget.initialUrl ?? widget.arguments!['initialUrl'];
  33. if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
  34. }
  35. @override
  36. void dispose() async {
  37. super.dispose();
  38. // 销毁 WebView 实例
  39. webViewController?.loadUrl('about:blank');
  40. webViewController?.clearCache();
  41. }
  42. @override
  43. void didUpdateWidget(covariant WebViewPage oldWidget) {
  44. super.didUpdateWidget(oldWidget);
  45. if (widget.initialUrl != oldWidget.initialUrl) {
  46. super.didUpdateWidget(oldWidget);
  47. }
  48. }
  49. // 注册js回调
  50. _invokeJavascriptChannel(BuildContext context) {
  51. return [
  52. JavascriptChannel(
  53. name: 'Invoke',
  54. onMessageReceived: (JavascriptMessage message) {
  55. // var webHeight = double.parse(message.message);
  56. // setState(() {
  57. // print('打印webHeight:$_webViewHeight');
  58. // _webViewHeight = webHeight;
  59. // });
  60. },
  61. ),
  62. JavascriptChannel(
  63. name: 'Invoke1',
  64. onMessageReceived: (JavascriptMessage message) {
  65. // var devicePixelRatio = double.parse(message.message);
  66. // setState(() {
  67. // _webViewHeight = devicePixelRatio;
  68. // });
  69. },
  70. ),
  71. JavascriptChannel(
  72. name: "integral",
  73. onMessageReceived: (JavascriptMessage message) {
  74. // print("交互");
  75. // print("参数: ${message.message}");
  76. // Map res = changeStringToJsonMap(message.message);
  77. // print(res["operation"]);
  78. // _controller?.evaluateJavascript("getAddressBook('sdad')");
  79. },
  80. ),
  81. JavascriptChannel(
  82. name: "MessageDeal",
  83. onMessageReceived: (JavascriptMessage message) async {
  84. // print("交互");
  85. // print("参数: ${message.message}");
  86. // print(webViewController);
  87. // webViewController
  88. // ?.runJavascriptReturningResult("showMessage('我(Flutter)收到了你的消息)");
  89. // _controller?.evaluateJavascript("document.title");
  90. },
  91. ),
  92. JavascriptChannel(
  93. name: "callWithDict",
  94. onMessageReceived: (JavascriptMessage message) {
  95. // print("交互");
  96. // print("参数: ${message.message}");
  97. },
  98. ),
  99. ];
  100. }
  101. // 获取页面高度
  102. _getWebViewHeight() async {
  103. // 方式一:
  104. // await webViewController?.runJavascriptReturningResult('''
  105. // try {
  106. // // Invoke.postMessage([document.body.clientHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight]);
  107. // let scrollHeight = document.documentElement.scrollHeight;
  108. // if (scrollHeight) {
  109. // Invoke.postMessage(scrollHeight);
  110. // }
  111. // } catch {}
  112. // ''');
  113. // 方式二:
  114. var originalHeight = await webViewController!.runJavascriptReturningResult("document.body.offsetHeight;");
  115. _webViewHeight = double.parse(originalHeight);
  116. setState(() {
  117. _webViewHeight = _webViewHeight <= 0 ? 300 : _webViewHeight;
  118. });
  119. // print('网页高度-----' + _webViewHeight.toString());
  120. }
  121. // 返回与后退的处理
  122. Future<bool> _onWillPop() async {
  123. if (webViewController == null) {
  124. Log.d("WebView都没有加载成功,直接返回退出即可");
  125. //WebView都没有加载成功,可以直接退出
  126. Navigator.of(context).pop();
  127. return false;
  128. } else {
  129. //如果点击了内部链接之后,可以返回,则直接返回
  130. if (await webViewController!.canGoBack()) {
  131. Log.d("点击内部链接,可以后台,调用后退");
  132. await webViewController!.goBack();
  133. return false; // 防止退出页面
  134. } else {
  135. Log.d("点击内部链接,无法后退,直接返回");
  136. Navigator.of(context).pop(); // 不能后退则退出当前页面
  137. return true;
  138. }
  139. }
  140. }
  141. @override
  142. Widget build(BuildContext context) {
  143. return Scaffold(
  144. backgroundColor: Colors.white,
  145. appBar:
  146. _showAppbar ? MyAppBar.appBar(context, title, backgroundColor: Colors.white, actions: widget.actions, backCallback: _onWillPop) : null,
  147. body: SafeArea(
  148. bottom: true,
  149. top: false,
  150. child: IndexedStack(
  151. index: _stackToView,
  152. children: [
  153. Column(
  154. children: [
  155. Expanded(
  156. child: WillPopScope(
  157. onWillPop: _onWillPop,
  158. child: WebView(
  159. key: _key,
  160. initialUrl: _initialUrl,
  161. userAgent: '',
  162. //JS执行模式 是否允许JS执行
  163. javascriptMode: JavascriptMode.unrestricted,
  164. //webview创建好
  165. onWebViewCreated: (WebViewController controller) {
  166. webViewController = controller;
  167. },
  168. onProgress: (int progress) {
  169. // print('progress=====>$progress');
  170. if (progress == 100) {
  171. Future.delayed(const Duration(milliseconds: 500)).then((value) => {
  172. // 获取页面高度
  173. _getWebViewHeight()
  174. });
  175. }
  176. },
  177. onPageStarted: (String url) {
  178. // print('onPageStarted=====>$url');
  179. },
  180. onPageFinished: (String url) {
  181. Log.d('onPageFinished=====>$url');
  182. if (mounted) {
  183. setState(() {
  184. _stackToView = 0;
  185. });
  186. }
  187. },
  188. onWebResourceError: (WebResourceError error) {
  189. // print('error=====>$error');
  190. },
  191. gestureNavigationEnabled: true,
  192. // 注册js 回调
  193. javascriptChannels: _invokeJavascriptChannel(context).toSet(),
  194. navigationDelegate: (NavigationRequest request) async {
  195. //如果是tel标签需要阻止并调用 launchUrl 的方法调用拨打电话
  196. if (request.url.startsWith('tel:')) {
  197. // 拦截tel链接
  198. if (!await launchUrl(Uri.parse(request.url))) throw '无法启动拨打电话功能';
  199. return NavigationDecision.prevent; // 阻止WebView导航到该链接
  200. }
  201. // 可以继续WebView的内部跳转
  202. return NavigationDecision.navigate;
  203. },
  204. ),
  205. ),
  206. )
  207. ],
  208. ),
  209. Container(
  210. color: Colors.white,
  211. child: Center(
  212. child: CircularProgressIndicator(
  213. strokeWidth: 3,
  214. valueColor: AlwaysStoppedAnimation(ColorConstants.appBlue),
  215. ),
  216. ),
  217. ),
  218. ],
  219. ),
  220. ));
  221. }
  222. }