iOS 原生项目嵌入 Flutter
虽然一般不建议在原生项目中嵌入 Flutter
,但是 Flutter
也可以支持这种方式,下面我们来看一下具体的实现。
原生嵌入 Flutter 的工程配置
如图,我们想使原生嵌入 Flutter
的话,使用 Android Studio
创建项目的时候就要选择 Module
进行创建,使之作为一个模块来开发。
打开我们新建的 flutter_module
工程目录可以看到,与创建的 Flutter App
相比,文件里面仍然有 Android
、iOS
工程文件,但是这里只是为了让我们做调试用的,而且这两个文件都是隐藏文件,不过 Android
、iOS
工程中不建议加入原生代码,而且即使加了,打包的时候也不会被打包进去。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
,使用终端,cd
到 NativeDemo
目录下,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
代表引擎对象,flutterVc
是 FlutterViewController
类型的控制器对象,msgChannel
是通信方式中的一种 channel
,为 FlutterBasicMessageChannel
类型,下面会有介绍。
在这里我们实现了 pushFlutter
与 pushFlutterTwo
两个方法,代表调起两个不同的 Flutter
页面。在这两个方法中,我们首先创建 methodChannel
对象,并分别传入 one
跟 two
两个字符串标识,并且 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 与原生的通信
MethodChannel
:Flutter
与Native
端相互调用,调用后可以返回结果,可以Native
端主动调用,也可以Flutter
主动调用,属于双向通信。此方式为最常用的方式,Native
端调用需要在主线程中执行。BasicMessageChannel
: 用于使用指定的编解码器对消息进行编码和解码,属于双向通信,可以Native
端主动调用,也可Flutter
主动调用。EventChannel
:用于数据流(event streams
)的通信,Native
端主动发送数据给Flutter
,通常用于状态的监听,比如网络变化、传感器数据等。
Flutter
与原生通信有三种方式,Flutter
为我们提供了三种 Channel
,分别是 MethodChannel
、BasicMessageChannel
与
EventChannel
。但是我们比较常用的就是 MethodChannel
与 BasicMessageChannel
这两种。因为 MethodChannel
前面已经讲过了,所以这里我们介绍一下 BasicMessageChannel
的用法。
BasicMessageChannel 用法
BasicMessageChannel
的用法与 FlutterMethodChannel
类似,在上面的代码示例中,首先在 Flutter
代码中我们也是定义一个 BasicMessageChannel
类型的变量 _messageChannel
,在 _messageChannel
的 setMessageHandler
闭包中接收来自于原生页面发来的消息,调用 _messageChannel
的 send
方法向原生页面进行通信,在输入框文字变化的时候都会调用 send
方法。在原生代码中也是类似,定义了 msgChannel
属性,setMessageHandler
中的 block
负责接收消息,sendMessage
发送消息,在 touchesBegan
中向 Flutter
传递 a
的累加值。BasicMessageChannel
与 FlutterMethodChannel
最大的区别就是 FlutterMethodChannel
是单次通讯,而 BasicMessageChannel
是持续通讯。