webview_page.dart 10 KB

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