webview_page.dart 9.6 KB

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