先提供demo,目录如下图
做到效果如下:
1、原生项目如何集成,配置flutter,实现混编
2、主工程是原生的老项目,调用一些flutter页面,实现页面跳转;
3、flutter页面主动与原生交互,传值给原生,并接受原生的回调;
4、原生页面主动与flutter页面交互,传值给flutter;
5、flutter页面,导航栏使用flutter自己的,可以返回
效果演示图
1、原生项目和flutter如何混合开发,如何配置
1、在和项目根目录上一级,平级目录下,新建一个flutter_module,怎么做如下
1、cd到指定目录
例如我的项目
2、执行如下命令:
flutter create -t module flutter_module
2、原生项目支持cocoapod
不支持的,在项目根目录
执行pod init
3、podfile文件中添加如下代码(没有的,请添加)
flutter_application_path = '../flutter_module/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')),binding)
4、执行pod install
5、项目配置bitcode为NO
6、新建run script phase
添加代码
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" buiObject-cld
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
7、拖动这个新建的run script到target dependencies 下面(看上图,在下面了)
到这里就完成了混编的集成配置,下面可以混编了
2、原生页面跳转到flutter,flutter页面主动传值给原生,并回调给flutter
1、跳转通过FlutterViewController
导入头文件#import <Flutter/Flutter.h>
//如果用到flutter插件时,导入下面头文件
import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
2、确定跳到那个页面
3、接受flutter主动传过来的方法名字,传的值,以及可以再次回调给flutter
FlutterMethodChannel
flutter代码
import 'package:flutter/material.dart';
//引入下面这个,是为了调用window的defaultRouteName拿到route判断跳转哪个界面
import 'dart:ui';
import 'package:flutter/services.dart';
import 'package:flutter_module/flutter_first.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
//根据原生给的民资,确定显示那个界面
Widget _widgetForRoute(String route){
switch (route){
case 'myApp':
return MyApp();
case 'home':
return FlutterFirst();
default:
return MyApp();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.pink,
),
routes: <String, WidgetBuilder>{
"/home":(BuildContext context) => FlutterFirst(),
},
home: MyHomePage(title: '我是flutter-->MyHomePage页面',),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title,}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
//创建一个给native的channel (类似iOS的通知)
static const methodChannel = const MethodChannel('wg/native_get');
//告诉ios,需要跳转到下一页了。ios自己跳,并传给ios一个字符串
_iosPushToVC() async {
//invokeMethod:两个参数Method:方法名(两端要统一),arguments:此方法名下需要传的参数
await methodChannel.invokeMethod('iOSFlutter', '这是flutter传的字符串');
}
//传给ios一个map
_iosGetMap() async {
Map<String, dynamic> value = {"code": "200", "data":[1,2,3]};
await methodChannel.invokeMapMethod('iOSFlutter1',value);
}
//拿到ios的返回值
_getIosValue() async {
dynamic result;
try{
result = await methodChannel.invokeMethod('iOSFlutter2');
}on PlatformException{
result = "error";
}
if(result is String){
showModalBottomSheet(context: context, builder: (BuildContext context){
return Container(
child: Center(
child: Text(result,style: TextStyle(color: Colors.brown), textAlign: TextAlign.center,),
),
height: 40,
);
});
}
}
@override
void initState() {
// TODO: implement initState
super.initState();
//添加观察者
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// TODO: implement didChangeAppLifecycleState
super.didChangeAppLifecycleState(state);
if(state != AppLifecycleState.resumed){
methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-show');
}else{
methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-hiden');
}
}
@override
void dispose() {
// TODO: implement dispose
WidgetsBinding.instance.removeObserver(this); //销毁观察者
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
// automaticallyImplyLeading: true,
leading: Container(
color: Colors.green,
child: RaisedButton(
onPressed: (){
methodChannel.invokeMethod('backToViewController');
},
child: Icon(
Icons.arrow_back,
// color: Colors.white,
),
),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
onTap: _iosPushToVC,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.forward),
Text('跳转ios新界面,参数是字符串'),
],
),
),
InkWell(
onTap: _iosGetMap,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.forward),
Text('传参,参数是map'),
],
)
),
InkWell(
onTap: _getIosValue,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.forward),
Text('接收iOS返回的内容'),
],
)
),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
ios代码
//
#define Screen_width [UIScreen mainScreen].bounds.size.width
#define Screen_height [UIScreen mainScreen].bounds.size.height
#import "ViewController.h"
#import <Flutter/Flutter.h>
//如果用到flutter插件时,导入下面头文件
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
#import "FirstViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
self.title = @"我是原生-ViewController页面";
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(15, 100, Screen_width-30, 30)];
btn.layer.cornerRadius = 15;
btn.layer.masksToBounds = YES;
btn.backgroundColor = UIColor.greenColor;
[btn setTitle:@"调用flutter页面,flutter主动和iOS交互" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)btnClicked{
[self pushToFlutterView];
}
- (void)pushToFlutterView{
FlutterViewController *flutterVC = [FlutterViewController new];
[flutterVC setInitialRoute:@"myApp"];
__weak __typeof(self) weakSelf = self;
NSString *channelName = @"wg/native_get";
//FlutterMethodChannel是flutter页面主动交互iOS页面
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterVC];
//flutter页面方法触发
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
// call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
// call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
// result 是给flutter的回调, 该回调只能使用一次
NSLog(@"---->call.method=%@ \n call.arguments = %@", call.method, call.arguments);
// method和WKWebView里面JS交互很像
// flutter点击事件执行后需要iOS做的事
if ([call.method isEqualToString:@"iOSFlutter"]) {
FirstViewController *firstVC = [[FirstViewController alloc] init];
[weakSelf.navigationController pushViewController:firstVC animated:YES];
}
// flutter给iOS传的参数
if ([call.method isEqualToString:@"iOSFlutter1"]) {
NSDictionary *dic = call.arguments;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"flutter页面传过来的字典" message:[NSString stringWithFormat:@"%@",dic] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}
//iOS给flutter返回值
if ([call.method isEqualToString:@"iOSFlutter2"]) {
if (result) {
result(@"这是iOS返回给flutter的内容");
}
}
if ([call.method isEqualToString:@"changeNavStatus"]) {
NSString *arguments = call.arguments;
if ([arguments containsString:@"show"]) {
weakSelf.navigationController.navigationBarHidden = NO;
}else{
weakSelf.navigationController.navigationBarHidden = YES;
}
}
if ([call.method isEqualToString:@"backToViewController"]) {
[weakSelf.navigationController popViewControllerAnimated:YES];
}
}];
//原生iOS跳转到Flutter页面
[self.navigationController pushViewController:flutterVC animated:YES];
}
@end
3、iOS主动传值给flutter
FlutterEventChannel
设置代理
实现代理
flutter代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class FlutterFirst extends StatefulWidget {
@override
_FlutterFirstState createState() => _FlutterFirstState();
}
class _FlutterFirstState extends State<FlutterFirst> with WidgetsBindingObserver {
// 注册一个通知,监听原生传给自己的值
static const EventChannel eventChannel = const EventChannel('wg/native_post');
static const MethodChannel methodChannel = const MethodChannel('wg/native_get');
// 渲染前的操作,类似viewDidLoad
@override
void initState() {
super.initState();
// 监听事件,同时发送参数12345
eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
//添加生命周期观察者
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
//销毁生命周期观察者
WidgetsBinding.instance.removeObserver(this);
}
String naviTitle = 'title';
// 回调事件
void _onEvent(Object event){
setState(() {
naviTitle = event.toString();
});
}
// 错误返回
void _onError(Object error) {
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// TODO: implement didChangeAppLifecycleState
super.didChangeAppLifecycleState(state);
if(state != AppLifecycleState.resumed){
methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-show');
}else{
methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-hiden');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '我是flutter-->FlutterFirst页面',
theme: ThemeData(
primarySwatch: Colors.pink
),
home: Material(
child: Scaffold(
appBar: AppBar(
title: Text('我是flutter-->FlutterFirst页面'),
leading: Container(
color: Colors.green,
child: RaisedButton(
onPressed: (){
methodChannel.invokeMethod('backToViewController');
},
child: Icon(
Icons.arrow_back,
// color: Colors.white,
),
),
),
),
body: Center(
child: Text(naviTitle),
),
),
),
);
}
}
iOS代码
#define Screen_width [UIScreen mainScreen].bounds.size.width
#define Screen_height [UIScreen mainScreen].bounds.size.height
#import "FirstViewController.h"
#import <Flutter/Flutter.h>
@interface FirstViewController ()<FlutterStreamHandler>
@property (nonatomic, copy) FlutterEventSink eventSink;
@end
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
self.title = @"我是原生FirstViewController页面";
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, Screen_width, 30)];
btn.layer.cornerRadius = 15;
btn.layer.masksToBounds = YES;
btn.backgroundColor = UIColor.redColor;
[btn setTitle:@"调用flutter页面-->iOS主动和flutter交互" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)btnClicked{
FlutterViewController *flutterVC = [FlutterViewController new];
flutterVC.navigationItem.title = @"Demo";
[flutterVC setInitialRoute:@"home"];
NSString *eventChannelName = @"wg/native_post";
NSString *methodChannelName = @"wg/native_get";
/*---------FlutterEventChannel是IOS主动与flutter交互-----------*/
FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:eventChannelName binaryMessenger:flutterVC];
// 代理FlutterStreamHandler
[eventChannel setStreamHandler:self];
/*---------FlutterEventChannel是IOS主动与flutter交互-----------*/
/*---------FlutterMethodChannel是flutter主动与IOS交互-----------*/
FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:methodChannelName binaryMessenger:flutterVC];
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqualToString:@"changeNavStatus"]) {
NSString *arguments = call.arguments;
if ([arguments containsString:@"show"]) {
self.navigationController.navigationBarHidden = NO;
}else{
self.navigationController.navigationBarHidden = YES;
}
}
if ([call.method isEqualToString:@"backToViewController"]) {
[self.navigationController popViewControllerAnimated:YES];
}
}];
/*---------FlutterMethodChannel是flutter主动与IOS交互-----------*/
[self.navigationController pushViewController:flutterVC animated:YES];
}
#pragma mark - <FlutterStreamHandler>
//这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体。
-(FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{
self.eventSink = events;
// arguments flutter监听时,给native传送的参数
if (self.eventSink) {
self.eventSink(@"原生push到fluttet页面,传给flutter的值");
}
return nil;
}
// flutter不再接收
-(FlutterError *)onCancelWithArguments:(id)arguments{
//
self.eventSink = nil;
return nil;
}
@end
导航栏控制
通过FlutterMethodChannel传值给iOS,让iOS实现原生导航的隐藏于显示
下载demo,运行时如果报
解决:
重新cd到原生项目根目录,执行pod install即可