view.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:html';
  4. import 'dart:math';
  5. import 'dart:typed_data';
  6. import 'dart:ui';
  7. import 'package:flutter/material.dart' hide Image;
  8. import 'package:flutter/services.dart';
  9. import 'package:webview_demo/utils/helper.dart';
  10. import 'package:webviewx/webviewx.dart';
  11. class WebViewXPage extends StatefulWidget {
  12. const WebViewXPage({
  13. Key? key,
  14. }) : super(key: key);
  15. @override
  16. _WebViewXPageState createState() => _WebViewXPageState();
  17. }
  18. class _WebViewXPageState extends State<WebViewXPage> {
  19. late WebViewXController webviewController;
  20. final executeJsErrorMessage = '脚本执行失败,请检查代码是否正确嵌入';
  21. /// 预加载的脚本文件
  22. List<String> scriptsFromAssets = [];
  23. Size get screenSize => MediaQuery.of(context).size;
  24. /// 预加载脚本文件
  25. Future<String> _preloadJS() async {
  26. final scriptsList = [
  27. "threejsview/js/build/three.js",
  28. "threejsview/js/build/THREE.MeshLine.js",
  29. "threejsview/js/controls/OrbitControls.js",
  30. "threejsview/js/gui/dat.gui.min.js",
  31. "threejsview/js/gui/stats.min.js",
  32. "threejsview/js/customScript/PointsConverter.js",
  33. "threejsview/js/customScript/Vector3Extention.js",
  34. "threejsview/js/customScript/EventCenter.js",
  35. "threejsview/js/customScript/CefInterface.js",
  36. "threejsview/js/customScript/InitPage.js",
  37. "threejsview/js/customScript/clipWorkflow/ClipMouseEvent.js",
  38. "threejsview/js/customScript/clipWorkflow/ClipMeshUpgrader.js",
  39. "threejsview/js/customScript/clipWorkflow/ClipDataDefine.js",
  40. "threejsview/js/customScript/clipWorkflow/ClipPlaneMover.js",
  41. "threejsview/js/customScript/clipWorkflow/ClipPlaneRotator.js",
  42. "threejsview/js/customScript/manager/CubeEdgesManager.js",
  43. "threejsview/js/customScript/manager/LinesDrawingManager.js",
  44. "threejsview/js/customScript/manager/ClipPlaneManager.js",
  45. "threejsview/js/customScript/manager/SpriteManager.js",
  46. "threejsview/js/customScript/utility/LoaderUtility.js",
  47. "threejsview/js/customScript/utility/LineIntersectionHelper.js",
  48. "threejsview/js/customScript/utility/PolyPointsTool.js",
  49. "threejsview/js/customScript/utility/CubeFaceNormalHelper.js",
  50. "threejsview/js/customScript/utility/OSHelper.js",
  51. ];
  52. for (String path in scriptsList) {
  53. var value = await _loadJSFromAssets(path);
  54. scriptsFromAssets.add(value);
  55. }
  56. return Future.value("");
  57. }
  58. Future<String> _loadJSFromAssets(String path) async {
  59. try {
  60. return await rootBundle.loadString(path);
  61. } catch (e) {
  62. return Future.error(e);
  63. }
  64. }
  65. String _loadRandomImage(int width, int height) {
  66. CanvasElement canvas = CanvasElement(width: width, height: height);
  67. var ctx = canvas.context2D
  68. ..fillStyle =
  69. "rgb(${Random().nextInt(255)},${Random().nextInt(255)},${Random().nextInt(255)})"
  70. ..fillRect(0, 0, width, height);
  71. var dataUrl = canvas.toDataUrl("image/jpeg", 0.95);
  72. return dataUrl;
  73. }
  74. @override
  75. void dispose() {
  76. webviewController.dispose();
  77. super.dispose();
  78. }
  79. @override
  80. Widget build(BuildContext context) {
  81. return Scaffold(
  82. appBar: AppBar(
  83. title: const Text('WebViewX Demo Page'),
  84. ),
  85. body: Center(
  86. child: Container(
  87. padding: const EdgeInsets.all(10.0),
  88. child: Row(
  89. children: [
  90. Expanded(
  91. flex: 2,
  92. child: Column(
  93. children: <Widget>[
  94. buildSpace(
  95. direction: Axis.vertical, amount: 10.0, flex: false),
  96. Container(
  97. padding: const EdgeInsets.only(bottom: 10.0),
  98. child: Text(
  99. '通过右侧的按钮操作 webview 注意!不可以在 webview 上面覆盖 Flutter 内容,会导致无法响应触摸',
  100. style: Theme.of(context).textTheme.bodyText2,
  101. ),
  102. ),
  103. buildSpace(
  104. direction: Axis.vertical, amount: 10.0, flex: false),
  105. Container(
  106. decoration: BoxDecoration(
  107. border: Border.all(width: 0.2),
  108. ),
  109. child: _buildWebViewX(),
  110. ),
  111. ],
  112. ),
  113. ),
  114. Expanded(
  115. child: Scrollbar(
  116. isAlwaysShown: true,
  117. child: SizedBox(
  118. width: 200,
  119. child: ListView(
  120. children: _buildButtons(),
  121. ),
  122. ),
  123. ),
  124. ),
  125. ],
  126. ),
  127. ),
  128. ),
  129. );
  130. }
  131. Widget _buildWebViewX() {
  132. return FutureBuilder<String>(
  133. future: _preloadJS(),
  134. builder: (context, AsyncSnapshot<String> snapshot) {
  135. if (snapshot.hasData) {
  136. return WebViewX(
  137. key: const ValueKey('webviewx'),
  138. initialContent: "<h2> Hello webviewX ! <h2>",
  139. initialSourceType: SourceType.html,
  140. height: screenSize.height / 1.5,
  141. width: min(screenSize.width * 0.8, 1024),
  142. onWebViewCreated: (controller) => webviewController = controller,
  143. onPageStarted: (src) =>
  144. debugPrint('A new page has started loading'),
  145. onPageFinished: (src) =>
  146. debugPrint('The page has finished loading'),
  147. jsContent: scriptsFromAssets
  148. .map((e) => EmbeddedJsContent(js: e))
  149. .toSet(),
  150. dartCallBacks: {
  151. DartCallback(
  152. name: 'TestDartCallback',
  153. callBack: (msg) => showSnackBar(msg.toString(), context),
  154. ),
  155. DartCallback(
  156. name: 'Dart_GetClipPlaneData',
  157. callBack: (msg) {
  158. final data = jsonDecode(msg);
  159. ///random color
  160. final imageData = _loadRandomImage(300, 300);
  161. /// result 需要传入 WorldPoints 列表(切面端点的三维坐标)、ImagePoints 列表(切面端点的二维坐标)、ImageData 图像路径(Base64)
  162. final result = {
  163. "ErrorCode": 1000,
  164. "WorldPoints": data["PointsList"],
  165. "ImagePoints": [
  166. {"X": 0.0, "Y": 1.0},
  167. {"X": 1.0, "Y": 1.0},
  168. {"X": 1.0, "Y": 0.0},
  169. {"X": 0.0, "Y": 0.0}
  170. ],
  171. "ImageData": imageData
  172. };
  173. return jsonEncode(result);
  174. },
  175. ),
  176. DartCallback(
  177. name: "Dart_GetStringData",
  178. callBack: (msg) {
  179. print("webview 触发 Dart_GetStringData :$msg");
  180. // showSnackBar(msg.toString(), context);
  181. return jsonEncode("{success:true}");
  182. },
  183. )
  184. },
  185. webSpecificParams: const WebSpecificParams(
  186. printDebugInfo: true,
  187. ),
  188. mobileSpecificParams: const MobileSpecificParams(
  189. androidEnableHybridComposition: true,
  190. ),
  191. navigationDelegate: (navigation) {
  192. debugPrint(navigation.content.sourceType.toString());
  193. return NavigationDecision.navigate;
  194. },
  195. );
  196. } else {
  197. return const CircularProgressIndicator();
  198. }
  199. });
  200. }
  201. List<Widget> _buildButtons() {
  202. return [
  203. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  204. Row(
  205. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  206. children: [
  207. Expanded(child: createButton(onTap: _goBack, text: '返回')),
  208. buildSpace(amount: 12, flex: false),
  209. Expanded(child: createButton(onTap: _goForward, text: '前进')),
  210. buildSpace(amount: 12, flex: false),
  211. Expanded(child: createButton(onTap: _reload, text: '重载')),
  212. ],
  213. ),
  214. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  215. createButton(
  216. text: '跳转至百度 (UrlBypass)',
  217. onTap: _setUrlBypass,
  218. ),
  219. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  220. createButton(
  221. text: '事件忽略开关 (点击、滚动等...)',
  222. onTap: _toggleIgnore,
  223. ),
  224. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  225. createButton(
  226. text: '在浏览器环境全局执行 2+2 计算,结果返回到 flutter',
  227. onTap: _evalRawJsInGlobalContext,
  228. ),
  229. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  230. const Text(
  231. "👇👇 Threejs 页面相关(建议顺序执行) 👇👇",
  232. style: TextStyle(fontSize: 20),
  233. ),
  234. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  235. createButton(
  236. text: '1. 载入页面 (从 assets 文件夹)',
  237. onTap: _setHtmlFromAssets,
  238. ),
  239. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  240. createButton(
  241. text: '2. 执行 initPage() (iframe 中)',
  242. onTap: _initPage,
  243. ),
  244. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  245. createButton(
  246. text: '3. 执行 setSurface(100,100,100,path) (iframe 中)',
  247. onTap: _setSurface,
  248. ),
  249. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  250. createButton(
  251. text: '4. 执行 mdlFileLoaded("Carotid","True") (iframe 中)',
  252. onTap: _mdlFileLoaded,
  253. ),
  254. buildSpace(direction: Axis.vertical, flex: false, amount: 20.0),
  255. createButton(
  256. text: '切换模型 changeSurface (iframe 中)',
  257. onTap: _changeSurface,
  258. ),
  259. ];
  260. }
  261. Future<void> _initPage() async {
  262. try {
  263. await webviewController.callJsMethod('initPage', []);
  264. } catch (e) {
  265. showAlertDialog(
  266. executeJsErrorMessage,
  267. context,
  268. );
  269. }
  270. }
  271. Future<void> _setSurface() async {
  272. try {
  273. await webviewController
  274. .callJsMethod('setSurface', [759, 596, 397, "../3DWeb/FaceImage"]);
  275. } catch (e) {
  276. showAlertDialog(
  277. executeJsErrorMessage,
  278. context,
  279. );
  280. }
  281. }
  282. Future<void> _changeSurface() async {
  283. final faceImages = [
  284. _loadRandomImage(500, 500),
  285. _loadRandomImage(500, 500),
  286. _loadRandomImage(500, 500),
  287. _loadRandomImage(500, 500),
  288. _loadRandomImage(500, 500),
  289. _loadRandomImage(500, 500),
  290. ];
  291. // print([759, 596, 397, ...faceImages]);
  292. try {
  293. await webviewController
  294. .callJsMethod('changeSurface', [759, 596, 397, ...faceImages]);
  295. } catch (e) {
  296. showAlertDialog(
  297. executeJsErrorMessage,
  298. context,
  299. );
  300. }
  301. }
  302. Future<void> _mdlFileLoaded() async {
  303. try {
  304. await webviewController
  305. .callJsMethod('mdlFileLoaded', ["Carotid", "True"]);
  306. } catch (e) {
  307. showAlertDialog(
  308. executeJsErrorMessage,
  309. context,
  310. );
  311. }
  312. }
  313. void _setUrlBypass() {
  314. webviewController.loadContent(
  315. 'https://www.baidu.com/',
  316. SourceType.urlBypass,
  317. );
  318. }
  319. Future<void> _setHtmlFromAssets() async {
  320. return await webviewController.loadContent(
  321. 'threejsview/MainPage.html',
  322. SourceType.html,
  323. fromAssets: true,
  324. );
  325. }
  326. Future<void> _goForward() async {
  327. if (await webviewController.canGoForward()) {
  328. await webviewController.goForward();
  329. showSnackBar('Did go forward', context);
  330. } else {
  331. showSnackBar('Cannot go forward', context);
  332. }
  333. }
  334. Future<void> _goBack() async {
  335. if (await webviewController.canGoBack()) {
  336. await webviewController.goBack();
  337. showSnackBar('Did go back', context);
  338. } else {
  339. showSnackBar('Cannot go back', context);
  340. }
  341. }
  342. void _reload() {
  343. webviewController.reload();
  344. }
  345. /// 事件忽略开关
  346. void _toggleIgnore() {
  347. final ignoring = webviewController.ignoresAllGestures;
  348. webviewController.setIgnoreAllGestures(!ignoring);
  349. showSnackBar('Ignore events = ${!ignoring}', context);
  350. }
  351. /// 在浏览器环境全局执行
  352. Future<void> _evalRawJsInGlobalContext() async {
  353. try {
  354. final result = await webviewController.evalRawJavascript(
  355. '2+2',
  356. inGlobalContext: true,
  357. );
  358. showSnackBar('The result is $result', context);
  359. } catch (e) {
  360. showAlertDialog(
  361. executeJsErrorMessage,
  362. context,
  363. );
  364. }
  365. }
  366. Widget buildSpace({
  367. Axis direction = Axis.horizontal,
  368. double amount = 0.2,
  369. bool flex = true,
  370. }) {
  371. return flex
  372. ? Flexible(
  373. child: FractionallySizedBox(
  374. widthFactor: direction == Axis.horizontal ? amount : null,
  375. heightFactor: direction == Axis.vertical ? amount : null,
  376. ),
  377. )
  378. : SizedBox(
  379. width: direction == Axis.horizontal ? amount : null,
  380. height: direction == Axis.vertical ? amount : null,
  381. );
  382. }
  383. }