前言
目前 Flutter 官方没有提供可用的 3D 接口,所以加载 3D 只能借助其他方式,如:WebGL、Unity、原生混合等。从实现效率及简易程度上考虑 WebGL 无疑是最简单的方案。
官方 webview_flutter 插件加载本地网页没有合适的 API 和防止跨域问题,利用 local_assets_server 插件启动本地 HTTP 服务器。
dart
void _initServer() async {
final server = LocalAssetsServer(
address: InternetAddress.loopbackIPv4,
assetsBasePath: 'assets',// 代理本地资源目录
logger: const DebugLogger(),
);
final address = await server.serve();
String url = 'http://${address.address}:${server.boundPort}';
}
Android 平台需满足 webview_flutter 插件的最低版本要求,打开 android/app/build.gradle 文件,设置 minSdkVersion >= 19。
xml
android {
...
defaultConfig {
...
minSdk = 24
}
...
}
默认情况下,移动端网络请求应通过 HTTPS 进行,以确保数据传输的安全性,需要配置应用允许 HTTP 请求。
Android 平台:打开 android/app/src/main/AndroidManifest.xml 文件,在 application 标签内添加 android:usesCleartextTraffic="true" 属性。
xml
<application
android:usesCleartextTraffic="true"
... >
...
</application>
IOS 平台: 打开 ios/Runner/Info.plist 文件,添加 io.flutter.embedded_views_preview 键并设置为 true。
xml
<plist version="1.0">
<dict>
...
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>
通过 WebGL 流行库 three.js 加载 3D,将相关代码和模型放入本地服务器代理的资源目录中。
Web 端默认情况下 dart:ui 和 dart:html 库是 web-only 的,不允许外部调用,需动态引入该方法。
dart
// 新建 dart_ui_web_fake.dart
final PlatformViewRegistry platformViewRegistry = PlatformViewRegistry();
class PlatformViewRegistry {
bool registerViewFactory(
String viewType,
Function viewFactory, {
bool isVisible = true,
}) =>
false;
}
// 新建 dart_web_fake.dart
class IFrameElement {
dynamic get style => null;
dynamic src;
}
移动端使用 webview_flutter,web 端使用 IFrameElement 加载本地网页
dart
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'shim/dart_ui_web_fake.dart' if (dart.library.ui_web) 'dart:ui_web' as ui_web;
import 'shim/dart_web_fake.dart' if (dart.library.js_interop) 'dart:html';
import 'package:local_assets_server/local_assets_server.dart';
class WebViewer extends StatefulWidget {
const WebViewer({super.key});
@override
State<StatefulWidget> createState() {
return WebViewerState();
}
}
class WebViewerState extends State<WebViewer> {
final String _uniqueViewType = 'web-viewer-${UniqueKey().toString()}';
bool _isListening = false;
String _webUrl = 'assets/web/index.html?name=model.glb';
@override
void initState() {
super.initState();
if (kIsWeb) {
_initWeb();
} else {
_initServer();
}
}
void _initWeb() {
ui_web.platformViewRegistry.registerViewFactory(
_uniqueViewType,
(viewId) => IFrameElement()
..style.border = 'none'
..style.height = '100%'
..style.width = '100%'
..src = _webUrl);
}
void _initServer() async {
final server = LocalAssetsServer(
address: InternetAddress.loopbackIPv4,
assetsBasePath: 'assets',
logger: const DebugLogger(),
);
final address = await server.serve();
setState(() {
_isListening = true;
_webUrl = _webUrl.replaceAll('assets','http://${address.address}:${server.boundPort}');
});
}
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return HtmlElementView(viewType: _uniqueViewType);
} else {
if (_isListening) {
return WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
print(progress);
},
onPageStarted: (String url) {
print(url);
},
onPageFinished: (String url) {
print(url);
},
onHttpError: (HttpResponseError error) {
print(error.response);
},
onWebResourceError: (WebResourceError error) {
print(error);
},
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(_webUrl)));
} else {
return const Center(child: CircularProgressIndicator());
}
}
}
}
在 pub 上有不少类似实现原理的插件,如 model_viewer_plus , o3d 等,还发现个基于 dart 和 three.js 编写的 three_dart 但因 gradle 版本过低应用不兼容报错。若简单展示模型可以看看。
该方案在性能方面只能说仁者见仁智者见智了。
完活。需要完整代码示例后台回复 Flutter 3D,后续有机会用 unity 实现一下,有用的话点个赞呗!!!