可以将两个Flutter module和宿主App克隆到同一个目录下面运行看看效果。
创建Flutter module
假定存在的Android原生项目目录是some/path/MyApp
,在MyApp所在的目录下创建一个flutter module
。
$ cd some/path/
$ flutter create -t module my_flutter
上面的命令会创建一个名为my_flutter
的flutter module,并且会在my_flutter
目录下生成一个./android
的隐藏子目录,这个目录的作用就是将创建好的flutter module包装成一个 android library
。
创建好的flutter module会为我们自动生成一个 main.dart
。我们可以直接使用Android studio
打开创建好的flutter module,先运行一下看下效果。
我们修改一下代码去掉顶部的AppBar,修改后页面如下图所示。
宿主app的要求
在连接flutter module和宿主app之前,你需要确保在宿主app的build.gradle
文件中声明如下
android {
//...
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
让宿主app依赖flutter module
在宿主app的settings.gradle
中把flutter module包含进来作为子项目
// MyApp/settings.gradle
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
然后在app/build.gradle
文件中声明如下
// MyApp/app/build.gradle
dependencies {
implementation project(':flutter')
//...
}
然后同步一下项目宿主工程MyApp。
在Java代码中使用Flutter module
使用 flutter module
使用flutter module生成的Java API 来添加 Flutter views 到宿主app中。flutter module生成的Java API 路径如下图所示。
- 可以直接使用
Flutter.createView
实现添加 Flutter views 到宿主app中。
宿主app的MainActivity的布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnFlutterCreateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用Flutter.createView方法"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnFlutterFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用FlutterFragment"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnFlutterCreateView" />
<FrameLayout
android:id="@+id/flContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnFlutterFragment" />
</android.support.constraint.ConstraintLayout>
当点击btnFlutterCreateView按钮的时候,我们创建一个flutterView,然后添加到布局文件中的flContainer中去。
// MyApp/app/src/main/java/some/package/MainActivity.java
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//创建flutterView
View flutterView = Flutter.createView(MainActivity.this, getLifecycle(), null);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, 800);
addContentView(flutterView, layout);
}
});
看下运行效果
- 使用
Flutter.createFragment
方法创建一个Fragment,使用Flutter.createFragment
方法我们不需要传递一个Lifecycle对象,FlutterFragment可以自己处理生命周期。
// MyApp/app/src/main/java/some/package/SomeActivity.java
private fun createFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.flContainer, Flutter.createFragment(null))
.commit()
}
运行效果是和上面的一样的,就不贴图了。
我们看一下Flutter.createView
和Flutter.createFragment
的方法签名。
public static FlutterView createView(final Activity activity, final Lifecycle lifecycle,
final String initialRoute) {
//...
}
public static FlutterFragment createFragment(String initialRoute) {
//...
}
这两个方法都有一个initialRoute
参数,这个是标记加载的flutter的初始界面。我们可以传递不同的initialRoute
来打开不同的界面。在flutter中,可以通过window.defaultRouteName
获取这个参数值。下面展示一个打开不同界面的例子。
- 在flutter_module中新建一个route1.dart
import 'package:flutter/material.dart';
///
/// Created by dumingwei on 2019/4/9.
/// Desc: rout1
///
class Route1App extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Route1(),
);
}
}
class Route1 extends StatefulWidget {
String text;
Route1({Key key, this.text}) : super(key: key);
@override
State createState() {
return Route1State();
}
}
class Route1State extends State<Route1> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Center(
child: Text(
'Hello world ,i am route1',
),
),
),
);
}
}
- 修改一下flutter module的
main.dart
flutter module的入口是lib/main.dart
。默认创建的widget是MyApp
。
import 'package:flutter/material.dart';
///lib/main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
///...
}
现在我们要根据传入的initialRoute
值来初始化widget。
import 'dart:ui';
import 'package:flutter/material.dart';
import 'route1.dart';
//调用window.defaultRouteName获取传入的传入的`initialRoute`值
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1'://创建Route1App
return Route1App();
default:
return MyApp();
}
}
//...
在上面的代码中如果传入的initialRoute
值是'route1',我们就初始化Route1App。
下面修改宿主app中MainActivity的代码。
// MyApp/app/src/main/java/some/package/SomeActivity.java
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnFlutterFragment.setOnClickListener {
createFragment()
}
}
private fun createFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.flContainer, Flutter.createFragment("route1"))
.commit()
}
在上面的代码中Flutter.createFragment()
方法我们传入的initialRoute是"route1"。
现在我们重新运行一下MyApp,效果如下。
热加载/热重启和调试Dart代码
完整的IDE集成以支持使用混合应用程序的Flutter / Dart代码的工作正在进行中。但是flutter命令行工具和Dart Observatory web user interface
已经提供了一些基本功能。
连接真机或者模拟器。然后使Flutter 命令行工具监听您的应用程序。
切换到flutter module的目录下,在命令行输入下面的命令
$ cd some/path/my_flutter
$ flutter attach
Waiting for a connection from Flutter on LLD AL20...
然后使用debug模式启动宿主app,然后导航到使用flutter的地方。然后回到命令行(我这里使用的命令行就是用Android Studio 打开 my_flutter,Android Studio中自带的命令行),可以看到类似下面输出的信息。
Done.
Syncing files to device LLD AL20... 1,752ms
🔥 To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R".
An Observatory debugger and profiler on LLD AL20 is available at: http://127.0.0.1:54043/
For a more detailed help message, press "h". To detach, press "d"; to quit, press "q".
现在你可以修改flutter中的代码,然后在命令行里输入r
来进行热更新。输入'R'来进行热重启(重新构建flutter widget的状态)。你也可以黏贴上面输出信息中的URL到浏览器中来使用Dart Observatory
来设置断点,分析内存保留以及其他调试任务。更多详细的帮助信息请输入h
,断开连接请输入d
,退出请输入q
。
我们先来试一试热更新。按照上面的步骤,使用debug的模式启动MyApp以后,我们点击使用Flutter.createView方法
按钮,输出如下。
然后我们修改my_flutter中的route1.dart
中显示的文字。
class Route1App extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Route1(),
);
}
}
class Route1 extends StatefulWidget {
String text;
Route1({Key key, this.text}) : super(key: key);
@override
State createState() {
return Route1State();
}
}
class Route1State extends State<Route1> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Center(
child: Text(
//修改文字
'Hello world ,i am route1 haha',
),
),
),
);
}
}
然后在命令行里输入r
。发现热更新起作用了。
关于Dart Observatory
以后再研究。
参考链接