import 'dart:async'; import 'dart:convert'; import 'dart:html'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart' hide Image; import 'package:flutter/services.dart'; import 'package:webview_demo/utils/helper.dart'; import 'package:webviewx/webviewx.dart'; class WebViewXPage extends StatefulWidget { const WebViewXPage({ Key? key, }) : super(key: key); @override _WebViewXPageState createState() => _WebViewXPageState(); } class _WebViewXPageState extends State { late WebViewXController webviewController; final executeJsErrorMessage = '脚本执行失败,请检查代码是否正确嵌入'; /// 预加载的脚本文件 List scriptsFromAssets = []; Size get screenSize => MediaQuery.of(context).size; /// 预加载脚本文件 Future _preloadJS() async { final scriptsList = [ "threejsview/js/build/three.js", "threejsview/js/build/THREE.MeshLine.js", "threejsview/js/controls/OrbitControls.js", "threejsview/js/gui/dat.gui.min.js", "threejsview/js/gui/stats.min.js", "threejsview/js/customScript/PointsConverter.js", "threejsview/js/customScript/Vector3Extention.js", "threejsview/js/customScript/EventCenter.js", "threejsview/js/customScript/CefInterface.js", "threejsview/js/customScript/InitPage.js", "threejsview/js/customScript/clipWorkflow/ClipMouseEvent.js", "threejsview/js/customScript/clipWorkflow/ClipMeshUpgrader.js", "threejsview/js/customScript/clipWorkflow/ClipDataDefine.js", "threejsview/js/customScript/clipWorkflow/ClipPlaneMover.js", "threejsview/js/customScript/clipWorkflow/ClipPlaneRotator.js", "threejsview/js/customScript/manager/CubeEdgesManager.js", "threejsview/js/customScript/manager/LinesDrawingManager.js", "threejsview/js/customScript/manager/ClipPlaneManager.js", "threejsview/js/customScript/manager/SpriteManager.js", "threejsview/js/customScript/utility/LoaderUtility.js", "threejsview/js/customScript/utility/LineIntersectionHelper.js", "threejsview/js/customScript/utility/PolyPointsTool.js", "threejsview/js/customScript/utility/CubeFaceNormalHelper.js", "threejsview/js/customScript/utility/OSHelper.js", ]; for (String path in scriptsList) { var value = await _loadJSFromAssets(path); scriptsFromAssets.add(value); } return Future.value(""); } Future _loadJSFromAssets(String path) async { try { return await rootBundle.loadString(path); } catch (e) { return Future.error(e); } } String _loadRandomImage(int width, int height) { CanvasElement canvas = CanvasElement(width: width, height: height); var ctx = canvas.context2D ..fillStyle = "rgb(${Random().nextInt(255)},${Random().nextInt(255)},${Random().nextInt(255)})" ..fillRect(0, 0, width, height); var dataUrl = canvas.toDataUrl("image/jpeg", 0.95); return dataUrl; } @override void dispose() { webviewController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('WebViewX Demo Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(10.0), child: Row( children: [ Expanded( flex: 2, child: Column( children: [ buildSpace( direction: Axis.vertical, amount: 10.0, flex: false), Container( padding: const EdgeInsets.only(bottom: 10.0), child: Text( '通过右侧的按钮操作 webview 注意!不可以在 webview 上面覆盖 Flutter 内容,会导致无法响应触摸', style: Theme.of(context).textTheme.bodyText2, ), ), buildSpace( direction: Axis.vertical, amount: 10.0, flex: false), Container( decoration: BoxDecoration( border: Border.all(width: 0.2), ), child: _buildWebViewX(), ), ], ), ), Expanded( child: Scrollbar( isAlwaysShown: true, child: SizedBox( width: 200, child: ListView( children: _buildButtons(), ), ), ), ), ], ), ), ), ); } Widget _buildWebViewX() { return FutureBuilder( future: _preloadJS(), builder: (context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return WebViewX( key: const ValueKey('webviewx'), initialContent: "

Hello webviewX !

", initialSourceType: SourceType.html, height: screenSize.height / 1.5, width: min(screenSize.width * 0.8, 1024), onWebViewCreated: (controller) => webviewController = controller, onPageStarted: (src) => debugPrint('A new page has started loading'), onPageFinished: (src) => debugPrint('The page has finished loading'), jsContent: scriptsFromAssets .map((e) => EmbeddedJsContent(js: e)) .toSet(), dartCallBacks: { DartCallback( name: 'TestDartCallback', callBack: (msg) => showSnackBar(msg.toString(), context), ), DartCallback( name: 'Dart_GetClipPlaneData', callBack: (msg) { final data = jsonDecode(msg); ///random color final imageData = _loadRandomImage(300, 300); /// result 需要传入 WorldPoints 列表(切面端点的三维坐标)、ImagePoints 列表(切面端点的二维坐标)、ImageData 图像路径(Base64) final result = { "ErrorCode": 1000, "WorldPoints": data["PointsList"], "ImagePoints": [ {"X": 0.0, "Y": 1.0}, {"X": 1.0, "Y": 1.0}, {"X": 1.0, "Y": 0.0}, {"X": 0.0, "Y": 0.0} ], "ImageData": imageData }; return jsonEncode(result); }, ), DartCallback( name: "Dart_GetStringData", callBack: (msg) { print("webview 触发 Dart_GetStringData :$msg"); // showSnackBar(msg.toString(), context); return jsonEncode("{success:true}"); }, ) }, webSpecificParams: const WebSpecificParams( printDebugInfo: true, ), mobileSpecificParams: const MobileSpecificParams( androidEnableHybridComposition: true, ), navigationDelegate: (navigation) { debugPrint(navigation.content.sourceType.toString()); return NavigationDecision.navigate; }, ); } else { return const CircularProgressIndicator(); } }); } List _buildButtons() { return [ buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: createButton(onTap: _goBack, text: '返回')), buildSpace(amount: 12, flex: false), Expanded(child: createButton(onTap: _goForward, text: '前进')), buildSpace(amount: 12, flex: false), Expanded(child: createButton(onTap: _reload, text: '重载')), ], ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '跳转至百度 (UrlBypass)', onTap: _setUrlBypass, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '事件忽略开关 (点击、滚动等...)', onTap: _toggleIgnore, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '在浏览器环境全局执行 2+2 计算,结果返回到 flutter', onTap: _evalRawJsInGlobalContext, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), const Text( "👇👇 Threejs 页面相关(建议顺序执行) 👇👇", style: TextStyle(fontSize: 20), ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '1. 载入页面 (从 assets 文件夹)', onTap: _setHtmlFromAssets, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '2. 执行 initPage() (iframe 中)', onTap: _initPage, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '3. 执行 setSurface(100,100,100,path) (iframe 中)', onTap: _setSurface, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '4. 执行 mdlFileLoaded("Carotid","True") (iframe 中)', onTap: _mdlFileLoaded, ), buildSpace(direction: Axis.vertical, flex: false, amount: 20.0), createButton( text: '切换模型 changeSurface (iframe 中)', onTap: _changeSurface, ), ]; } Future _initPage() async { try { await webviewController.callJsMethod('initPage', []); } catch (e) { showAlertDialog( executeJsErrorMessage, context, ); } } Future _setSurface() async { try { await webviewController .callJsMethod('setSurface', [759, 596, 397, "../3DWeb/FaceImage"]); } catch (e) { showAlertDialog( executeJsErrorMessage, context, ); } } Future _changeSurface() async { final faceImages = [ _loadRandomImage(500, 500), _loadRandomImage(500, 500), _loadRandomImage(500, 500), _loadRandomImage(500, 500), _loadRandomImage(500, 500), _loadRandomImage(500, 500), ]; // print([759, 596, 397, ...faceImages]); try { await webviewController .callJsMethod('changeSurface', [759, 596, 397, ...faceImages]); } catch (e) { showAlertDialog( executeJsErrorMessage, context, ); } } Future _mdlFileLoaded() async { try { await webviewController .callJsMethod('mdlFileLoaded', ["Carotid", "True"]); } catch (e) { showAlertDialog( executeJsErrorMessage, context, ); } } void _setUrlBypass() { webviewController.loadContent( 'https://www.baidu.com/', SourceType.urlBypass, ); } Future _setHtmlFromAssets() async { return await webviewController.loadContent( 'threejsview/MainPage.html', SourceType.html, fromAssets: true, ); } Future _goForward() async { if (await webviewController.canGoForward()) { await webviewController.goForward(); showSnackBar('Did go forward', context); } else { showSnackBar('Cannot go forward', context); } } Future _goBack() async { if (await webviewController.canGoBack()) { await webviewController.goBack(); showSnackBar('Did go back', context); } else { showSnackBar('Cannot go back', context); } } void _reload() { webviewController.reload(); } /// 事件忽略开关 void _toggleIgnore() { final ignoring = webviewController.ignoresAllGestures; webviewController.setIgnoreAllGestures(!ignoring); showSnackBar('Ignore events = ${!ignoring}', context); } /// 在浏览器环境全局执行 Future _evalRawJsInGlobalContext() async { try { final result = await webviewController.evalRawJavascript( '2+2', inGlobalContext: true, ); showSnackBar('The result is $result', context); } catch (e) { showAlertDialog( executeJsErrorMessage, context, ); } } Widget buildSpace({ Axis direction = Axis.horizontal, double amount = 0.2, bool flex = true, }) { return flex ? Flexible( child: FractionallySizedBox( widthFactor: direction == Axis.horizontal ? amount : null, heightFactor: direction == Axis.vertical ? amount : null, ), ) : SizedBox( width: direction == Axis.horizontal ? amount : null, height: direction == Axis.vertical ? amount : null, ); } }