1.1作为独立页面加入

这是以页面级作为独立的模块加入,而不是页面的某个元素。

  • 原生页面可以打开Flutter页面
  • Flutter页面可以打开原生页面

Flutter基础系列之混合开发(二)

1.2作为页面的一部分嵌入

比如说原生页面中只有某一个item是Flutter;

  • 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。

Flutter基础系列之混合开发(二)

第四步:添加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下面,接下来就可以运行项目了。

Flutter基础系列之混合开发(二)

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)
            ],
          ),
        )
    );
  }
}
View Code

相关文章: