Flutter加载3D方案-WebGL

前言

目前 Flutter 官方没有提供可用的 3D 接口,所以加载 3D 只能借助其他方式,如:WebGL、Unity、原生混合等。从实现效率及简易程度上考虑 WebGL 无疑是最简单的方案。

官方 webview_flutter 插件加载本地网页没有合适的 API 和防止跨域问题,利用 local_assets_server 插件启动本地 HTTP 服务器。

  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。

android {
    ...
    defaultConfig {
        ...
        minSdk = 24
    }
    ...
}

默认情况下,移动端网络请求应通过 HTTPS 进行,以确保数据传输的安全性,需要配置应用允许 HTTP 请求。

Android 平台:打开 android/app/src/main/AndroidManifest.xml 文件,在 application 标签内添加 android:usesCleartextTraffic=“true” 属性。

<application
    android:usesCleartextTraffic="true"
    ... >
    ...
</application>

IOS 平台:
打开 ios/Runner/Info.plist 文件,添加 io.flutter.embedded_views_preview 键并设置为 true。

<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_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 加载本地网页

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});

  
  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';

  
  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}');
    });
  }

  
  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 实现一下,有用的话点个赞呗!!!

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值