通过学习本片文章中的知识点,你可以避免掉很多坑,从而轻松的实现 Flutter 的集成。

简介

1. Kotlin

Kotlin,由 JetBrains 于 2011.07 推出,一款面向 JVM 在 Java 虚拟机上运行的静态类型编程语言。

相比 Java,它可以静态检测很多陷阱,比如常见多发的空指针,所以开发效率更高。

而且通过支持variable type inference,higher-order functions (closures),extension
functions,

mixins and first-class delegation等实现,使得它比 Java 更加简洁。虽然它与 Java 语法并不兼容,

但 Kotlin 可以和 Java 代码相互运作。更为重要的是,

在 2017 年的 Goofle I/O 上,也宣布 kotlin 为 Android 的官方开发语言。

github 地址:Kotlin <https://github.com/JetBrains/kotlin>

2. MVP

在这里,MVP 就不再赘述,在我的上一篇文章,已经详细介绍过了。

demo 里的是 Kotlin 版,但实现原理都是一样的。

有兴趣的点下方链接:

从 0 到 1,带你解剖 MVP 的神秘之处,并自己动手实现 MVP !
<https://juejin.im/post/5b75491ef265da283719d0e7>

3. Flutter

Flutter,由 Google 在 2018. 02 推出的移动UI框架,

可以快速在 Android 和 iOS 上构建高质量的原生用户界面。

Flutter 的优势,在这里我也不再多说了。在 Flutter 中文网 <https://flutterchina.club/> 都是有的。

优势有很多,当然劣势也很多!虽说跨平台,但是对于适配问题,还需要去优化并解决。

性能相关,经常会出现一些卡顿现象,并且对于动画的实现效果,也不是那么的理想。

当然,还有很多其他的问题。毕竟现在发布的也只是 beta 版,上述的这些问题,也会得到很好的解决的。

ok,下面切入正题,我们如何在项目中,去使用 Flutter。

疑问

在 Android 原有项目的基础,去集成并使用 Flutter,肯定会有下面几个疑问?

*
如何在原生上,展示 Flutter 界面?

*
原生如何给 Flutter 传送数据?Flutter 如何接收?

*
Flutter 如何调用原生的 method ?通过什么来调用?

*
我们知道在 Flutter 中,主入口只有一个 void main(),

如果在原生界面 A,要显示一个 ListView。在原生界面 B,要显示一个 webView。

那我们在 Flutter 中,通过什么来判断我要加载的是 ListView 还是 webView 呢?

实现

ps:如果电脑前的同学没有安装 Flutter,建议先安装。

Flutter 下载安装地址 <https://flutterchina.club/get-started/install/>

1. 在 Android 原生的项目基础中,如何集成 Flutter

*
打开你的项目,找到 Terminal,输入终端命令:flutter channel

如果输出如下:

我们需要切换到 master 分支,继续输入终端命令:flutter channel master,

等待执行完毕之后,我们就成功的切换到了 master 分支。为什么要切换到 master 分支?

因为我们在安装 Flutter 的时候,默认安装的是 beta 版本。

该版本,目前是不支持在现有项目中集成 Flutter Module 模块功能的。

如果在 beta 版本中,执行了创建 Module 命令:flutter create -t module 你要创建的库的名字,

它会提示你 "module" is not an allowed value for option "template" 。

*
执行终端命令,创建你的 Flutter Library:flutter create -t module flutter_library。

等待执行,创建成功后,会如下所示:

注意:命令中的 flutter_library, 是我对 Flutter Library 的命名。你可以替换为你的命名。

*
将 flutter_library 添加到 Android 工程

找到 Project 层 setting.gradle 文件并打开,添加如下代码:
setBinding(new Binding([gradle: this])) evaluate(new File(
settingsDir.parentFile,
'/你的工程目录名/flutter_library/.android/include_flutter.groovy' ))
编译通过后,在 app 目录下的 build.gradle,添加依赖:
dependencies { implementation project(':flutter') }
至此,我么已经成功将 Flutter Module 添加到 Android 工程中了。是不是很简单?skr skr skr ……

2. 在原生上,如何展示 Flutter 界面?

打开我们 app 目录下的 MainActivity,添加如下代码:
addContentView(Flutter.createView(this, lifecycle, "route1"), FrameLayout
.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams
.MATCH_PARENT));
以上代码,就是创建了一个宽高均充满屏幕的 FlutterView,可以将 FlutterView 看作为展示 Flutter Widget 的容器。

”route1“ 是什么鬼?这个待会儿再解释,现在你不需要关心。现在运行代码,会看到如下所示:


现在呢,我们已经成功在原生上,将 Flutter 界面成功的展示出来。

3. 原生如何给 Flutter 传送数据?Flutter 如何接收?

在这里,我们需要用到 EventChannel。

这个类的作用,可以简单理解为从原生向 Flutter,push data:主动的推送数据。

修改后的 Activity 代码如下:
class MainActivity : AppCompatActivity() { companion object { val
GET_NAME_CHANNEL ="sample.flutter.io/get_name" } override fun
onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) val flutterView = Flutter.createView
(this, lifecycle,"route1") addContentView(flutterView, FrameLayout.LayoutParams
( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))
; EventChannel(flutterView, GET_NAME_CHANNEL).setStreamHandler(object :
EventChannel.StreamHandler { override fun onListen(p0: Any?, events:
EventChannel.EventSink?) { events?.success(getName()) } override fun
onCancel(p0: Any?) { } }) } fun getName(): String? ="flutter_library" }
看 Flutter 端接收的代码:
class MyHomePage extends StatefulWidget { final String title; MyHomePage({Key
key,this.title}) : super(key: key); @override _MyHomePageState createState() =>
new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> {
static const EventChannel eventChannel = EventChannel(
'sample.flutter.io/get_name'); String _name = 'unknown'; void _receiveData() {}
@overridevoid initState() { super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError); }void
_onEvent(Object event) { setState(() { _name = event.toString(); }); }void
_onError(Object error) { setState(() { _name ='Battery status: unknown.'; }); }
@override Widget build(BuildContext context) {return new Scaffold( appBar: new
AppBar( title:new Text(widget.title), ), body: new Center( child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[new
Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[new
Text('Flutter', key: const Key('Battery level label')), new Padding( padding:
const EdgeInsets.all(16.0), child: new RaisedButton( child: const Text('Refresh'
), onPressed: _receiveData, ), ), ], ),new Text('从原生 push 过来的数据:' + _name), ],
), ), ); } }
注意:在创建 EventChannel 对象的时候,传入的 name,

一定要和你在原生中传入的 name 对应起来,否则将接收不到。这个很好理解。

4. Flutter 如何调用原生的 method ?通过什么来调用?

MethodChannel :

当 Flutter 向原生调用方法或获取数据时,需要用到这个类来实现。

接下来看 Android 端实现代码,修改后如下:
class MainActivity : AppCompatActivity() { companion object { val PUSH_CHANNEL
="sample.flutter.io/push" val PULL_CHANNEL = "sample.flutter.io/pull" }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate
(savedInstanceState) setContentView(R.layout.activity_main) val flutterView =
Flutter.createView(this, lifecycle, "route1") addContentView(flutterView,
FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout
.LayoutParams.MATCH_PARENT)); EventChannel(flutterView, PUSH_CHANNEL)
.setStreamHandler(object : EventChannel.StreamHandler { override fun
onListen(p0: Any?, events: EventChannel.EventSink?) { events?.success
(getName()) } override fun onCancel(p0: Any?) { } }) MethodChannel(flutterView,
PULL_CHANNEL).setMethodCallHandler { methodCall, result -> run { if (methodCall
.method.equals("refresh")) { refresh() result.success("") } else { result
.notImplemented() } } } } fun getName(): String? = "flutter_library" fun
refresh() { showShort("refresh") } }
当 Flutter 调用 refresh 方法时,android 端调用 refresh() 方法,这里实现了一个简单的吐司,并返回了空字符串。

当然你也可以做其他操作,比如跳转页面、实现动画、获取数据等等。

5. 判断不同的 route ,加载不同的界面

我们在 MainActivity 加载 FlutterView 时,有传入一个参数 "route1"。

点击进入 createView 的源码时,有这样一句注释:
The default initialRoute is "/".


通过查看源码得知,initialRoute 的默认值为 "/"。因为入口只有一个:void main(),

所以判断 route ,加载不同界面的逻辑应该也就在这里了。具体请看代码实现:
void main() => runApp(new MyApp(window.defaultRouteName)); class MyApp extends
StatelessWidget { final String route; MyApp(this.route); @override Widget
build(BuildContext context) {switch (route) { case "route1": return new
MaterialApp( title:"Android-Flutter-Demo", home: new MyHomePage(title:
'Android-Flutter-Demo'), ); break; default: return Center( child: Text('Unknown
route: $route', textDirection: TextDirection.ltr), ); } } }
怎么样,很简单的吧?到这里呢,文章开头说的那四个问题,我们也都一一解决掉了。

下面说一下我的 demo 实现,在 Android 端获取接口数据,然后转化成 json 格式,

通过 Flutter 端的调用,以列表形式进行展示。最后效果图如下:



demo 中的代码实现,没有考虑实际需求。

只是为了验证,android 和 flutter 混合开发,这条路是行得通的。

最后,奉上 github demo 地址:

Android-Flutter-Demo <https://github.com/JD-CP/Android-Flutter-Demo>

喜欢的同学可以点点 star ~~~

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信