Flutter混编:在Android原生中混编Flutter

直到写这篇文章的时候(2018.11.15),通过网上已有的资料可以知道Flutter目前在与原生代码的混编这一块基本还处于不可用的状态,切换的时候还会黑一下屏,性能比较差,但是这个问题随着Flutter的发展未来是肯定可以解决的,所以目前还在Flutter探索道路一起前行的小伙伴没必要“闻混色变”。

扯点题外话,目前《闲鱼》客户端已经在商品详情页使用纯Flutter编写了,单页面纯Flutter写是没有问题的,在这里顺便提一下怎么简单辨认一个页面是Flutter还是原生(不严谨,在你知道它是有Flutter的情况下,因为RN也会这样的):

  1. 打开手机的“开发者模式”
  2. 打开“显示布局边界”
  3. 切回APP

以《闲鱼》商品列表页和商品详情页为例:

商品列表页和商品详情页

接下来我们打开布局边界显示,可以看到闲鱼的商品列表页(左边)是原生写的,所以可以看到布局层,颜色越深表示布局层数越多,而商品详情页(右边)是Flutter写的,只有一层布局:

布局边界

好,题外话扯完,下面开始在Android中来混编Flutter,就使用之前的《日报》的例子。

计划是这样的:

  • 将外层UI框架,包括toolbar,转移到原生中来处理
  • 将ListView,日报数据请求逻辑等由Flutter来处理

这基本可以是一个次简单的混编例子。

最简单的。。。还要数Flutter官方的那个例子(生肉警告):https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps

新建Flutter Module

想在Native代码中混编Flutter,需要在Flutter中建立一个Module,官方通过命令行创建,但Android Studio的Flutter插件同样提供了这个功能,而且更加直观,所以这里以Android Studio为例:

新建Flutter Module

接下来项目路径,名称等:

Flutter Module路径配置

之后设置包名,再点击finish就好了。

新建Android工程

在Flutter Module同一父目录下新建Android工程,如果已有的工程也可以,同样道理。

新建Android工程

之后一路next下去。

Android工程中引入Flutter

在建立的Android工程的settings.gradle中加入以下代码:

1
2
3
4
5
setBinding(new Binding([gradle: this]))                                 
evaluate(new File(
settingsDir.parentFile,
'flutter_daily_module/.android/include_flutter.groovy'
))

其中settingsDir.parentFile表示当前目录的父级目录,flutter_daily_module是前面所建立的Flutter Module目录。

Sync一下就可以看到多了一个Flutter的library module

Flutter Module

之后在Application Module的Build.gradle中依赖刚刚引入的library:

1
implementation project(':flutter')

Android原生代码调用Flutter

先来一个简单的Demo。

我们把Flutter中的代码改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'dart:ui';
import 'package:flutter/material.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return Center(
child: Text('route: $route', textDirection: TextDirection.ltr),
);
case 'route2':
return Center(
child: Text('route: $route', textDirection: TextDirection.ltr),
);
default:
return Center(
child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
);
}
}

上面代码中会返回一个Widget,如果传入参数为route1就会在widget居中位置显示route: router1

在Android项目默认生成的MainActivity中,我们来展示一个route1。

1
2
3
4
5
6
7
8
9
View flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"route1"
);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
layout.leftMargin = 100;
layout.topMargin = 400;
addContentView(flutterView, layout);

运行原生Android工程,可以看到如下效果:

FlutterView运行效果

FlutterView不是唯一的使用方式,还有一种通过FlutterFragment来调用Flutter的代码方式,如下代码所示:

1
2
3
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fl_flutter_view, Flutter.createFragment("route1"));
fragmentTransaction.commit();

其中fl_flutter_view对应Android中在xml中定义的一个FrameLayout:

1
2
3
4
<FrameLayout
android:id="@+id/fl_flutter_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

运行效果图如下所示:

FlutterFragment运行效果

Android原生外层框架中嵌入Flutter日报列表

完成上面步骤以后,我们已经了解在Android中是怎么调用Flutter的,下面把实战系列文章的《日报》列表页嵌入这个“外框”中。

《日报》纯Flutter项目源码:
https://github.com/KevinWu1993/DailyFlutter

我们先把Flutter中列表页的主要代码同步到flutter_daily_module中。

原本《日报》的main.dart代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:flutter/material.dart';
import 'package:zhihudaily/daily/daily_page.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '日报',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DailyPage(title: '日报'),
);
}
}

Flutter Module中main.dart我们改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_daily_module/daily/daily_page.dart';

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
switch (route) {
case 'dailyInNative':
return MaterialApp(
title: '日报',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DailyPage(title: '日报', isShowToolbar: false,),
);
default:
return MaterialApp(
title: '日报',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DailyPage(title: '日报'),
);
}
}

要注意的是default选项,因为Flutter Module也是可以独立运行的,所以留着这个case。

同样的在DailyPage这一个Widget的构造方法里面,增加一个可选“是否显示Toolbar”的参数,目的是在独立运行flutter项目的时候显示状态栏,而在作为library module混编进Android的时候隐藏Toolbar,使用Android原生的Toolbar。

1
DailyPage({Key key, this.title, this.isShowToolbar = true}) : super(key: key);

对于Flutter Toolbar,在DailyPage中构建的方法如下:

1
2
3
4
5
6
7
8
Widget _buildAppBar(BuildContext buildContext) {
if (widget.isShowToolbar)
return new AppBar(
title: Text(widget.title),
);
else
return null;
}

完成上述步骤后,在Android原生代码中调用如下:

1
2
3
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fl_flutter_view, Flutter.createFragment("dailyInNative"));
fragmentTransaction.commit();

到此就完成了日报列表的嵌入,那么我们来运行一下:

原生运行效果

而当使用Flutter独立模式运行的时候,它是这样的:

Flutter独立运行效果

无图无真相,以一个布局边界对比图来结束这篇文章吧,注意看Toolbar,一个是原生的,一个是Flutter的:

布局边界对比

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/964c6c3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu.CN
支付宝打赏