更新记录

1.0.4(2021-09-26)

  • 升级Flutter 2.5.1

1.0.3(2021-06-30)

  • 升级Flutter SDK 2.2.2
  • 支持集成Flutter插件

1.0.2(2021-04-12)

  • 修复iOS nvue组件进入后台,返回后无响应问题
  • 升级Flutter SDK 2.0.4
查看更多

平台兼容性

Android Android CPU类型 iOS
适用版本区间:5.0 - 11.0 armeabi-v7a:未测试,arm64-v8a:未测试,x86:未测试 适用版本区间:9 - 15

原生插件通用使用流程:

  1. 购买插件,选择该插件绑定的项目。
  2. 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
  3. 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
  4. 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
  5. 开发完毕后正式云打包

付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios

注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择


使用文档 (更多插件及问题,+微信:zhuzheVIP1)

重要提示:

该插件已经支持集成任何Flutter插件,包括webf(北海Kraken)。
详细集成,如有不懂,请联系个人微信:zhuzheVIP1

该插件不再更新,推荐使用离线打包方式:

demo

插件详细文档

由于篇幅有限,详情文档请看:

详细文档

插件说明

此插件提供了 sn-flutter 模块和 sn-flutter-view 组件(仅 nvue 使用)

插件示例

  • uniapp: https://github.com/snice/uniapp-flutter
  • flutter: https://github.com/snice/uniapp-flutter-module

温馨提示

  • flutter 开发与 uniapp 开发都是独立的,插件只是为了把 2 者揉在一起
  • 为了 iOS 能够云打包(不超过 40M),ios 插件仅支持 arm64;如有需要支持 armv7,请加群获取

Flutter 开发说明

Flutter 插件是基于多个页面或视图特性

  • 创建 flutter module

  • 开发

    需要使用提供的模板

    channel/uniapp.dart

    class UniappMethodChannel {
    MethodChannel _channel;
    BuildContext _context;
    Map<String, Function(Map<String, dynamic> map)> _methodHandlers = new Map();
    bool isInit = false;
    double _sh = 0;
    
    double get statusBarHeight {
      var su = ScreenUtil();
      if (su != null && su.statusBarHeight > 0) {
        return su.statusBarHeight;
      } else {
        if (_sh > 0) return _sh;
        return 30;
      }
    }
    
    void initChannel() {
      if (isInit) return;
      isInit = true;
      _channel = MethodChannel('com.itfenbao.uniapp');
      _channel.setMethodCallHandler((MethodCall call) async {
        if (call.method == "canPop") {
          _channel.invokeMethod("canPop", Navigator.canPop(_context));
        } else {
          if (_methodHandlers.containsKey(call.method)) {
            if (call.arguments != null) {
              Function.apply(_methodHandlers[call.method],
                  [new Map<String, dynamic>.from(call.arguments)]);
            } else {
              Function.apply(_methodHandlers[call.method], []);
            }
          } else {
            throw Exception('not implemented ${call.method}');
          }
        }
      });
    }
    
    void setContext(BuildContext context) {
      _context = context;
      _sh = MediaQuery.of(context).padding.top;
    }
    
    BuildContext getContext() {
      return _context;
    }
    
    void push(Route route) {
      Navigator.push(_context, route);
      this.fireEvent("canPop", true);
    }
    
    void pushNamed(String routeName) {
      Navigator.pushNamed(_context, routeName);
    }
    
    bool canPop() {
      return Navigator.canPop(_context);
    }
    
    void pop() {
      if (canPop()) {
        Navigator.maybePop(_context);
        Future.delayed(const Duration(milliseconds: 10), () {
          this.fireEvent("canPop", Navigator.canPop(_context));
        });
      } else {
        fireEvent("pop");
      }
    }
    
    ///
    /// 监听uniapp事件
    ///
    void $on(String method, Function(Map<String, dynamic> map) handler) {
      _methodHandlers[method] = handler;
    }
    
    ///
    /// 取消uniapp事件监听
    ///
    void $off(String method) {
      _methodHandlers.remove(method);
    }
    
    ///
    /// 给uniapp发送事件
    ///
    void $emit(String eventName, [dynamic arguments]) {
      fireEvent(eventName, arguments);
    }
    
    ///
    /// 给uniapp发送事件(同步)
    ///
    Future $emitSync(String eventName, [dynamic arguments]) {
      String alphabet = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM';
      int strlenght = 25;
    
      /// 生成的字符串固定长度
      String left = "";
      for (var i = 0; i < strlenght; i++) {
        left = left + alphabet[Random().nextInt(alphabet.length)];
      }
      var params = new Map<String, dynamic>.from(arguments);
      params["callbackId"] = eventName + "&" + left;
      return fireEvent(eventName, params);
    }
    
    ///
    /// 调用uniapp回调
    ///
    void callback(String callbackId, [dynamic arguments]) {
      var params = new Map<String, dynamic>.from(arguments);
      params["callbackId"] = callbackId;
      fireEvent('_uni_callback', params);
    }
    
    ///
    /// 调用uniapp回调(持久回调)
    ///
    void callbackKeepAlive(String callbackId, [dynamic arguments]) {
      var params = new Map<String, dynamic>.from(arguments);
      params["callbackId"] = callbackId;
      params["keepAlive"] = true;
      fireEvent('_uni_callback', params);
    }
    
    Future fireEvent(String eventName, [dynamic arguments]) {
      if (isInit) {
        return _channel.invokeMethod(eventName, arguments);
      }
      return Future.value("");
    }
    }
    
  • 调试

    使用 flutter 调试即可

  • 打包

    • android

    flutter build aar

    拷贝 build/host/outputs/repo/xx/flutter_release/1.0/flutter_release-1.0.aar 到 插件 android 目录下

    • ios

    flutter build ios-framework --xcframework --no-universal --output output/ios

    拷贝 App.framework 到 插件 ios 目录下

  • 工具

    • ios framework 检测工具

    检测 framework 文件是否动态库、包含的架构 下载后,放到*.framewrok 文件同级目录运行即可

    检测

manifest.json 配置注意

  • minSdkVersion

    21

sn-flutter 模块

方法

  • cachePages

    缓存页面,可以提前执行 flutter,提升页面打开速度

    const flutter = uni.requireNativePlugin("sn-flutter");
    flutter.cachePages({
    pages: ["main", "topMain", "bottomMain"],
    });

    main, topMain,bottomMain 都是入口函数

    void main() => runApp(MyApp(Colors.blue));
    
    @pragma('vm:entry-point')
    void topMain() => runApp(MyApp(Colors.green));
    
    @pragma('vm:entry-point')
    void bottomMain() => runApp(MyApp(Colors.purple));
    
  • openPage

    打开 flutter 页面。仅支持 uniapp page -> flutter page -> flutter page。

    flutter.openPage({
    id: "main",
    });

sn-flutter-view 组件

  sn-flutter-view 是一个 nvue 组件,仅用于 nvue 页面

属性

属性 类型 默认值 说明
entryPoint string main 入口函数
destroyAfterBack boolean true 是否返回页面销毁 flutter

事件

  • pop

    pop 调用

  • popChange

    pop 变化

    popChange({ detail }) {
    // 是否可以pop,默认是不可以pop
    this.canPop = detail.pop;
    }

方法

  • pop

    pop 路由

使用

<template>
    <sn-flutter-view ref="flutter" :instanceId="instanceId" :entryPoint="entryPoint" :params="params" destroyAfterBack="true" @pop="onPop" @popChange="popChange" style="flex:1;" />
</template>

<script>
import { MethodChannel } from '@/js/flutter/flutter.js';
export default {
    data() {
        return {
            canPop: false,
            instanceId: 'topMain',
            entryPoint: 'main',
            params: {
                a: 1
            }
        };
    },
    onBackPress() {
        if (this.canPop) {
            this.$refs.flutter.pop();
            return true;
        }
    },
    onLoad() {
        this.methodChannel = new MethodChannel(this.instanceId);
        this.methodChannel.$on('test', e => {
            uni.showToast({
                title: JSON.stringify(e),
                icon: 'none'
            });
            if (e.callbackId) {
                this.methodChannel.callback(e.callbackId, { result: 3 });
            }
        });
    },
    onUnload() {
        this.methodChannel.$off('test');
    },
    methods: {
        onPop() {
            uni.navigateBack();
        },
        popChange({ detail }) {
            this.canPop = detail.pop;
        }
    }
};
</script>

<style></style>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问