美文网首页Flutter
iOS 原生项目嵌入 Flutter

iOS 原生项目嵌入 Flutter

作者: 晨曦的简书 | 来源:发表于2021-12-01 13:33 被阅读0次

    虽然一般不建议在原生项目中嵌入 Flutter,但是 Flutter 也可以支持这种方式,下面我们来看一下具体的实现。

    原生嵌入 Flutter 的工程配置

    如图,我们想使原生嵌入 Flutter 的话,使用 Android Studio 创建项目的时候就要选择 Module 进行创建,使之作为一个模块来开发。

    打开我们新建的 flutter_module 工程目录可以看到,与创建的 Flutter App 相比,文件里面仍然有 AndroidiOS 工程文件,但是这里只是为了让我们做调试用的,而且这两个文件都是隐藏文件,不过 AndroidiOS 工程中不建议加入原生代码,而且即使加了,打包的时候也不会被打包进去。flutter_module 是一个纯 Flutter 的工程。

    • Podfile 文件配置
    flutter_application_path = '../flutter_module'
    load File.join(flutter_application_path,'.iOS','Flutter','podhelper.rb')
    
    platform :ios, '9.0'
    
    target 'NativeDemo' do
      install_all_flutter_pods(flutter_application_path)
      use_frameworks!
    
      # Pods for NativeDemo
    
    end
    

    我们使用 Xcode 创建一个原生工程,NativeDemo,使用终端,cdNativeDemo 目录下,pod init,然后配置 Podfile 文件,然后执行 pod install

    pod install 完成之后,打开原生项目,引用头文件 #import <Flutter/Flutter.h>,可以成功的话就代表配置成功,现在的话原生工程与 Flutter 就有联系了,下面我们就可以实现代码了,来使原生工程中嵌入 Flutter

    原生项目调起 Flutter 页面

    • 原生代码部分
    #import "ViewController.h"
    #import <Flutter/Flutter.h>
    
    @interface ViewController ()
    @property(nonatomic, strong) FlutterEngine* flutterEngine;
    @property(nonatomic, strong) FlutterViewController* flutterVc;
    @property(nonatomic, strong) FlutterBasicMessageChannel * msgChannel;
    @end
    
    @implementation ViewController
    
    -(FlutterEngine *)flutterEngine
    {
        if (!_flutterEngine) {
            FlutterEngine * engine = [[FlutterEngine alloc] initWithName:@"hank"];
            if (engine.run) {
                _flutterEngine = engine;
            }
        }
        return _flutterEngine;
    }
    
    - (IBAction)pushFlutter:(id)sender {
        
        self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen;
    
        //创建channel
        FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:@"one_page" binaryMessenger:self.flutterVc.binaryMessenger];
        //告诉Flutter对应的页面
        [methodChannel invokeMethod:@"one" arguments:nil];
        
        //弹出页面
        [self presentViewController:self.flutterVc animated:YES completion:nil];
        
        //监听退出
        [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            //如果是exit我就退出页面!
            if ([call.method isEqualToString:@"exit"]) {
                [self.flutterVc dismissViewControllerAnimated:YES completion:nil];
            }
        }];
    }
    - (IBAction)pushFlutterTwo:(id)sender {
        self.flutterVc.modalPresentationStyle = UIModalPresentationFullScreen;
    
        //创建channel
        FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:@"two_page" binaryMessenger:self.flutterVc.binaryMessenger];
        //告诉Flutter对应的页面
        [methodChannel invokeMethod:@"two" arguments:nil];
        
        //弹出页面
        [self presentViewController:self.flutterVc animated:YES completion:nil];
        
        //监听退出
        [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            //如果是exit我就退出页面!
            if ([call.method isEqualToString:@"exit"]) {
                [self.flutterVc dismissViewControllerAnimated:YES completion:nil];
            }
        }];
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.flutterVc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
        self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVc.binaryMessenger];
        
        [self.msgChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {
            NSLog(@"收到Flutter的:%@",message);
        }];
        
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        static int a = 0;
        [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]];
    }
    

    在原生代码部分我们定义了三个属性,flutterEngine 代表引擎对象,flutterVcFlutterViewController 类型的控制器对象,msgChannel 是通信方式中的一种 channel,为 FlutterBasicMessageChannel 类型,下面会有介绍。

    在这里我们实现了 pushFlutterpushFlutterTwo 两个方法,代表调起两个不同的 Flutter 页面。在这两个方法中,我们首先创建 methodChannel 对象,并分别传入 onetwo 两个字符串标识,并且 binaryMessenger 传参传入的都是 self.flutterVc.binaryMessenger。在两个方法中分别调用 invokeMethod 方法,向 Flutter 页面发送消息,然后弹出页面,并且实现 setMethodCallHandler 方法,在闭包中判断 call.method isEqualToString:@"exit",进行页面的退出。

    • Flutter 代码部分
    // ignore_for_file: avoid_print
    
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatefulWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      final MethodChannel _oneChannel = const MethodChannel('one_page');
      final MethodChannel _twoChannel = const MethodChannel('two_page');
      final BasicMessageChannel _messageChannel =
          const BasicMessageChannel('messageChannel', StandardMessageCodec());
    
      String pageIndex = 'one';
    
      @override
      void initState() {
        super.initState();
    
        _messageChannel.setMessageHandler((message) {
          print('收到来自iOS的$message');
          return Future(() {});
        });
    
        _oneChannel.setMethodCallHandler((call) {
          pageIndex = call.method;
          print(call.method);
          setState(() {});
          return Future(() {});
        });
        _twoChannel.setMethodCallHandler((call) {
          pageIndex = call.method;
          print(call.method);
          setState(() {});
          return Future(() {});
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: _rootPage(pageIndex),
        );
      }
    
      //根据pageIndex来返回页面!
      Widget _rootPage(String pageIndex) {
        switch (pageIndex) {
          case 'one':
            return Scaffold(
              appBar: AppBar(
                title: Text(pageIndex),
              ),
              body: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () {
                      _oneChannel.invokeMapMethod('exit');
                    },
                    child: Text(pageIndex),
                  ),
                  TextField(
                    onChanged: (String str) {
                      _messageChannel.send(str);
                    },
                  )
                ],
              ),
            );
          case 'two':
            return Scaffold(
              appBar: AppBar(
                title: Text(pageIndex),
              ),
              body: Center(
                child: ElevatedButton(
                  onPressed: () {
                    _twoChannel.invokeMapMethod('exit');
                  },
                  child: Text(pageIndex),
                ),
              ),
            );
          default:
            return Scaffold(
              appBar: AppBar(
                title: Text(pageIndex),
              ),
              body: Center(
                child: ElevatedButton(
                  onPressed: () {
                    const MethodChannel('default_page').invokeMapMethod('exit');
                  },
                  child: Text(pageIndex),
                ),
              ),
            );
        }
      }
    }
    

    Flutter 代码中我们定义了 _oneChannel_twoChannel 这两个变量用了接收原生页面发送的消息,并且向原生页面发送消息。定义了变量 pageIndex 用来标识创建那个页面。

    initState 方法中调用 setMethodCallHandler 方法,获取到原生页面传来的数据并赋值给 pageIndex,然后调用 setState 方法。

    build 方法中我们调用 _rootPage 方法来判断创建哪个页面。并且分别在这两个页面的点击事件中调用 invokeMapMethod 方法,代表退出页面,原生页面在 setMethodCallHandler 闭包中接收到 exit 数据后就会调用 [self.flutterVc dismissViewControllerAnimated:YES completion:nil],进行页面的退出。

    Flutter 与原生的通信

    • MethodChannelFlutterNative 端相互调用,调用后可以返回结果,可以 Native 端主动调用,也可以 Flutter 主动调用,属于双向通信。此方式为最常用的方式, Native 端调用需要在主线程中执行。
    • BasicMessageChannel: 用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以 Native 端主动调用,也可 Flutter 主动调用。
    • EventChannel:用于数据流(event streams)的通信, Native 端主动发送数据给 Flutter,通常用于状态的监听,比如网络变化、传感器数据等。

    Flutter 与原生通信有三种方式,Flutter 为我们提供了三种 Channel,分别是 MethodChannelBasicMessageChannel
    EventChannel。但是我们比较常用的就是 MethodChannelBasicMessageChannel 这两种。因为 MethodChannel 前面已经讲过了,所以这里我们介绍一下 BasicMessageChannel 的用法。

    BasicMessageChannel 用法

    BasicMessageChannel 的用法与 FlutterMethodChannel 类似,在上面的代码示例中,首先在 Flutter 代码中我们也是定义一个 BasicMessageChannel
    类型的变量 _messageChannel,在 _messageChannelsetMessageHandler 闭包中接收来自于原生页面发来的消息,调用 _messageChannelsend 方法向原生页面进行通信,在输入框文字变化的时候都会调用 send 方法。在原生代码中也是类似,定义了 msgChannel 属性,setMessageHandler 中的 block 负责接收消息,sendMessage 发送消息,在 touchesBegan 中向 Flutter 传递 a 的累加值。BasicMessageChannelFlutterMethodChannel 最大的区别就是 FlutterMethodChannel 是单次通讯,而 BasicMessageChannel 是持续通讯。

    相关文章

      网友评论

        本文标题:iOS 原生项目嵌入 Flutter

        本文链接:https://www.haomeiwen.com/subject/ovrjxrtx.html