关于 FLutter显示原生视图 View(iOS 篇)并交互

终端创建 flutter项目:flutter create plugin(项目名称)

终端上 cd 到此目录下
image.png
执行:
flutter build ios --no-codesign
pod install

在 ios 中创建需要的 iOS 原生视图(创建 flutter 项目后,项目中会有iOS 项目文件夹与 Android 项目文件夹), 打开iOS 项目。

① 创建原生视图,可提供外界调用的方法 / 属性等。

import Lottie //在 pod 中引入的庫
import SnapKit

class LottieView: UIView {
    // MARK: - UI
    private lazy var animationView: AnimationView = {
        let aniView = AnimationView()
        aniView.loopMode = .loop
        aniView.backgroundColor = .clear
        return aniView
    }()
    
    // MARK: - Property
    
    // MARK: - init
    init(frame: CGRect, jsonName: String) {
        super.init(frame: frame)
        animationView.animation = Animation.named(jsonName)
        
        setupUI()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
extension LottieView {
    func setupUI() {
        addSubview(animationView)
        animationView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
    }
}

extension LottieView {
    func ae_play(competionBlock: LottieCompletionBlock? = nil) {
        animationView.play(completion: competionBlock)
    }
    
    func ae_pause() {
        animationView.pause()
    }
    
    func ae_stop() {
        animationView.stop()
    }
}

② 创建遵守 FlutterPlatformViewFactory协议的 NSObject class 类

//代码:
class LottieFactory: NSObject, FlutterPlatformViewFactory {
    
    let messenger: FlutterBinaryMessenger
    let lottieType:LottieType
    
    //自定义初始化(在 AppDelegate 中注册创建)
    init(messenger: FlutterBinaryMessenger, lottieType: LottieType) {
        self.messenger = messenger
        self.lottieType = lottieType
        
        super.init()
    }
    
    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
        // 返回遵守 FlutterPlatformView 协议的自定义初始化类
        return LottiePlatformView(frame: frame, viewId: viewId, arguments: args, messenger: self.messenger, lottieType: self.lottieType)
    }
    
    // Only needs to be implemented if `createWithFrame` needs an arguments parameter.
    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
}

③ 创建遵守 FlutterPlatformView 协议的 NSObject class 类

代码:
class LottiePlatformView: NSObject, FlutterPlatformView {
    // MARK: - UI
    private let lottieView: LottieView //自定义原生视图
    
    // MARK: - Property
    let CHANNEL_NAME: String = "Kit.LottieView.viewId_" // 多个视图时 利用 viewId
    
    // MARK: - init
    init(frame: CGRect = .zero, viewId: Int64, arguments: Any?, messenger: FlutterBinaryMessenger, lottieType: LottieType) {
        var jsonName: String = "file_pick_wave"
        
        if lottieType == .sleep {
            jsonName = "file_asleep"
        } else if lottieType == .surprise {
            jsonName = "file_pick_surprise"
        }
        self.lottieView = LottieView(frame: frame, jsonName: jsonName)
        
        super.init()
        
        // 交互事件
        let channel = FlutterMethodChannel(name: CHANNEL_NAME + "\(viewId)", binaryMessenger: messenger)
         //与flutter 交互的方法通道(FlutterMethodChannel:由于FlutterMethodChannel(flutter 与 原生来回一次)只能一个来回传递. 用于方法调用(调用一次,反应一次)
        // 使用 FlutterEventChannel(原生会不中断(非一次来回)发送数据到 flutter,只要原生有响应,flutter 就会有响应)用于数据流(event streams)的通信,(监听,发送))
        channel.setMethodCallHandler { [weak self] call, result in
            if call.method == "play" {
                self?.lottieView.ae_play() //原生视图提供的函数
                result(true)
            } else if call.method == "pause" {
                self?.lottieView.ae_pause()
                result(true)
            } else if call.method == "stop" {
                self?.lottieView.ae_stop()
                result(true)
            } else {
                result(FlutterMethodNotImplemented)
            }
        }
    }
    
    func view() -> UIView {
        return self.lottieView
    }
}

④ 在 Appdelegate 中注册

    if let register = self.registrar(forPlugin: "lottie_view_plugin") {
        register.register(LottieFactory(messenger: register.messenger(), lottieType: .wave), withId: "wave_lottie_view")
        register.register(LottieFactory(messenger: register.messenger(), lottieType: .surprise), withId: "surprise_lottie_view")
        register.register(LottieFactory(messenger: register.messenger(), lottieType: .sleep), withId: "sleep_lottie_view")
    }

Flutter

创建个通信 dart 文件

import 'package:flutter/services.dart';

class WaveLottieManager {
  static MethodChannel? _wave_channel;

  static void createChannel(int viewId) {
    _wave_channel ??= MethodChannel("Kit.LottieView.viewId_$viewId");
  }
  
  static Future<bool> play() async {
    //交互回调方法(注意 play 必须与原生的中 call.method == ''play'' 一致)
    bool result = await WaveLottieManage._wave_channel?.invokeMethod("play");
    return result;
  }

  static Future<bool> pause() async {
    bool result = await WaveLottieManage._wave_channel?.invokeMethod("pause");
    return result;
  }

  static Future<bool> stop() async {
    bool result = await WaveLottieManage._wave_channel?.invokeMethod("stop");
    return result;
  }
}

dart 文件中使用:

class WaveLottieView extends StatelessWidget {
  const WaveLottieView({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: kScreenW,
      height: kScreenH,
      child: UiKitView(
        viewType: "wave_lottie_view", //注意:此处必须与AppDelegate 中注册此视图的标识一致
        creationParamsCodec: const StandardMessageCodec(),
        onPlatformViewCreated: (viewId) {
          WaveLottieManager.createChannel(viewId); //创建交互通道(注意通道的命名必须与原生中通道命名得保持一致)
        },
      ),
    );
  }
}
为了保证用户界面在交互过程中的流畅性,无论是从Flutter向Native端发送消息, 还是Native向Flutter发送消息都是以异步的形式进行传递的。

######在交互中,原生有三类 channel:
① MethodChannel:用于传递方法调用(method invocation):MethodChannel使用场景:无论是Flutter端还是Native端都可以通过MethodChannel向对方平台发送两端提前定义好的方法名来调用对方平台相对应的消息处理逻辑并且带回返回值给被调用方。

② EventChannel:用于事件流的发送(event streams):更侧重于Native平台主动向Flutter平台,单向给Flutter平台发送消息,Flutter无法返回任何数据给Native端,EventChannel描述是单通的。可以类比Android里面的广播 ,iOS 中得通知。
    EventChannel 通信特点:
    1、用于 iOS 端向 Flutter 端传递事件.
    2、单向通信, 只能是 iOS 发送事件, Flutter 监听.
    3、可持续性通信.

③ BasicMessageChannel:用于传递字符串和半结构化的消息:比如flutter想拍照,拍完照后的图片路径需要传给flutter,照片的路径发送可以使用BasicMessageChannel.Reply回复,也可以使用sendMessage主动再发一次消息。个人认为接收消息并回复消息属于一次通信,所以倾向于使用BasicMessageChannel.Reply。


与flutter 交互的方法通道(FlutterMethodChannel:由于FlutterMethodChannel(flutter 与 原生来回一次)只能一个来回传递. 用于方法调用(调用一次,反应一次)。
使用 FlutterEventChannel(原生会不中断(非一次来回)发送数据到 flutter,只要原生有响应,flutter 就会有响应)用于数据流(event streams)的通信,(监听,发送))。
关于 FlutterEventChannel 使用
public class SwiftSoundPlugin: NSObject, FlutterPlugin {
    static let OIFI_CHANNEL_Method = "method_channel"
    private let OIFI_CHANNEL_EVENT = "event_channel"
    var eventSink: FlutterEventSink?
    
    init(with registrar: FlutterPluginRegistrar) {
        super.init()
        
        let channel_event = FlutterEventChannel(name: OIFI_CHANNEL_EVENT, binaryMessenger: registrar.messenger())
        channel_event.setStreamHandler(self)
    }
    
    public static func register(with registrar: FlutterPluginRegistrar) {
        let channel_method = FlutterMethodChannel(name: OIFI_CHANNEL_Method, binaryMessenger: registrar.messenger())
        registrar.addMethodCallDelegate(SwiftSoundPlugin(with: registrar), channel: channel_method)
    }
    
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        if call.method == "initPartnerID" {
            guard let dict = call.arguments as? Dictionary<String, Any> else {
                return
            }
            let partnerId = dict["partnerId"] as! String
            let privateKey = dict["privateKey"] as! String
            OIFIManager.shared.initPartnerID(partnerId: partnerId, privateKey: privateKey) { value in
                result(value >= 0);
            }
        } else if call.method == "startSound" {
            result(OIFIManager.shared.startListener());
        } else if call.method == "stopSound" {
            result(OIFIManager.shared.stopListener());
        } else {
            result(FlutterMethodNotImplemented);
        }
    }
}

extension SwiftSoundPlugin: FlutterStreamHandler {
    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events;

        //OIFIManager.shared.receiveListenerSound 原生方法(这里就不展示了)
        OIFIManager.shared.receiveListenerSound { message, soundData in
            guard soundData != nil else {
                events(nil)
                return
            }
            events(message);
        }
        
        return nil
    }
    
    public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        self.eventSink = nil
        return nil
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容