1.1作为独立页面加入
这是以页面级作为独立的模块加入,而不是页面的某个元素。
- 原生页面可以打开Flutter页面
- Flutter页面可以打开原生页面
1.2作为页面的一部分嵌入
比如说原生页面中只有某一个item是Flutter;
- Flutter页面中只有某一部分是原生视图
2.Flutter混合开发的集成步骤
2.1创建Flutter Module
在做混合开发之前,我们首先需要创建一个Flutter Module。
这里建议Flutter Module的创建目录和原生工程的目录同级。假设Native的目录是这样的:xxx/Native项目。
cd xxx/
flutter create -t module flutter_module
可以看到生成的flutter_module目录下有这些文件:
README.md pubspec.lock
flutter_module.iml pubspec.yaml
flutter_module_android.iml test
.android lib .ios
上面的.android和.ios目录,是隐藏文件, 也是这个flutter_module的宿主工程。因为有宿主工程的存在,这个flutter_module在不添加额外配置的情况下是可以独立运行的:
- .android:flutter_module的Android宿主工程;
- .ios:flutter_module的iOS宿主工程;
- lib:flutter_module的Dart部分的代码;
- pubspec.yaml:flutter_module的项目依赖配置文件。
2.2添加Flutter Module依赖:为原生项目添加Flutter的依赖
官方解决方案:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
2.2.1为已存在的iOS原生项目添加Flutter Module依赖
【说明】:在原生项目中添加Flutter Module,需要配置好CocoaPods到工程中。如果没有使用CocoaPods的,可以参考https://www.cnblogs.com/LeeGof/p/5737551.html进行配置。
第一步:在Podfile文件中添加依赖:
flutter_application_path = "../flutter_module" eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
第二步:安装依赖:
在项目的根目录中,执行如下指令:
pod install
第三步:禁用Bitcode:
目前Flutter还不支持Bitcode,所以集成了Flutter的iOS项目需要禁用Bitcode。
第四步:添加Build Phase来构建Dart代码。
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
添加完之后,要将这个Run Script拖动到Target Dependencies phase下面,接下来就可以运行项目了。
2.2.2为已存在的Android应用添加Flutter Module依赖
第一步:配置Android项目的Flutter Module依赖:打开Android项目的settings.gradle添加如下代码。这段脚本的作用是让Flutter作为一个单独的模块打包进来。
//HybridAndroid/settings.gradle setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'flutter_module/.android/include_flutter.groovy' ))
setBinding和evaluate允许Flutter模块包括它自己在内的任何Flutter插件,在settings.gradle中以类似::flutter、package_info、:video_player的方式存在。
第二步:添加:flutter依赖。
//app/build.gradle //... dependencies { //... implementation project(':flutter') }
在build.gradle中配置的时候,有两个地方要特别注意:
compileOptions { //编译需要设置成JAVA8 sourceCompatibility 1.8 targetCompatibility 1.8 } defaultConfig { minSdkVersion 16 //Flutter中要求最低SDK版本为16 //... }
2.3在Java/OC中调用Flutter Module
2.3.1OC中调用Flutter Module
在OC中调用Flutter Module有两种方式:
- 直接使用FlutterViewController的方式;
- 使用FlutterEngine的方式。
下面我们分别来看一下这两种方式。
2.3.1.1直接使用FlutterViewController
FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; [GeneratedPluginRegistrant registerWithRegistry:flutterViewController]; //如果使用了插件 [flutterViewController setInitialRoute:@"myApp"]; [self.navigationController pushViewController:flutterViewController animated:YES];
通过上面的代码,我们可以看到setInitialRoute方法传递了参数“myApp”,该参数用于告诉Dart代码显示哪个Flutter视图。在Flutter Module的main.dart文件中,需要通过window.defaultRouteName来获取Native指定要显示的路由名,以确定要创建哪个窗口小部件并传递给runApp:
void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'myApp': return MyApp(); default: return MaterialApp( home: Center( child: Text('没找到'), ), ); } }
2.3.1.2使用FlutterEngine的方式
第一步:需要AppDelegate继承自FlutterAppDelegate
//AppDelegate.h #import <UIKit/UIKit.h> #import <Flutter/Flutter.h> @interface AppDelegate : FlutterAppDelegate @property (strong, nonatomic) FlutterEngine *flutterEngine; @end //AppDelegate.m - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //FlutterEngine初始化 self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil]; [self.flutterEngine runWithEntrypoint:nil]; [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; //有插件 //设置RootVC self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UIViewController *vc = [[ViewController alloc] init]; UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; self.window.rootViewController = nav; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; }
第二步:通过FlutterEngine来初始化FlutterViewController。
FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine]; FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil]; [self.navigationController pushViewController:flutterViewController animated:YES];
因为在AppDelegate中,我们已经提前初始化了FlutterEngine,所以这种方式打开一个Flutter模块的速度,比第一种方式要快一些。
【注意】:使用FlutterEngine方式,调用 setInitialRoute 方法会无效,在Flutter端拿到的永远是“I”,这是Flutter SDK的一个BUG,因此如果必须依赖 setInitialRoute 参数,那么只能使用方式一进行赋值。
2.3.2Java中调用Flutter Module
在Java中调用Flutter Module有两种方式:
- 使用Flutter.createView API的方式;
- 使用FlutterFragment的方式。
2.3.2.1使用Flutter.createView API的方式
fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { View flutterView = Flutter.createView( MainActivity.this, getLifecycle(), "myApp" ); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800); layout.leftMargin = 100; layout.topMargin = 200; addContentView(flutterView, layout); } });
2.3.2.2FlutterFragment的方式
fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); tx.replace(R.id.someContainer, Flutter.createFragment("myApp")); tx.commit(); } });
上面都使用了字符串“myApp”来告诉Dart代码,在Flutter视图中显示哪个widget。在Flutter项目中可以通过 window.defaultRouteName 来获取Native传过来的“myApp”字符串,以确定要创建哪个widget并传递给runApp。
2.4编写Dart代码
import 'package:flutter/material.dart'; import 'dart:ui'; import 'package:flutter/services.dart'; void main() => runApp(_widgetForRoute(window.defaultRouteName)); Widget _widgetForRoute(String route) { switch (route) { case 'myApp': return new MyApp(); default: return Center( child: Text('Unknown route: $route', textDirection: TextDirection.ltr), ); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter 混合开发'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('gof.flutter.io/battery'); String _batteryLevel = 'Unknown battery level.'; Future<Null> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch(e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ RaisedButton( child: Text('Get Battery Level'), onPressed: _getBatteryLevel, ), Text(_batteryLevel) ], ), ) ); } }