Kotlin + MVP + Flutter ,让你能够在本身的项目中集成 Flutter 并使用

经过学习本片文章中的知识点,你能够避免掉不少坑,从而轻松的实现 Flutter 在 Android 项目中的集成。android

简介

1. Kotlingit

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

相比 Java,它能够静态检测不少陷阱,好比常见多发的空指针,因此开发效率更高。web

并且经过支持variable type inference,higher-order functions (closures),extension functions,编程

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

但 Kotlin 能够和 Java 代码相互运做。更为重要的是,bash

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

github 地址:Kotlin框架

2. MVPless

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

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

有兴趣的点下方连接:

从 0 到 1,带你解剖 MVP 的神秘之处,并本身动手实现 MVP !

3. Flutter

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

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

Flutter 的优点,在这里我也再也不多说了。在 Flutter 中文网 都是有的。

优点有不少,固然劣势也不少!虽然说跨平台,可是对于适配问题,还须要去优化并解决。

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

固然,还有不少其余的问题。毕竟如今发布的也只是 beta 版,上述的这些问题,也会获得很好的解决的。

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

疑问

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

  1. 如何在原生上,展现 Flutter 界面?

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

  3. Flutter 如何调用原生的 method ?经过什么来调用?

  4. 咱们知道在 Flutter 中,主入口只有一个 void main()

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

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

实现

ps:若是电脑前的同窗没有安装 Flutter,建议先安装。

Flutter 下载安装地址

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

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

    默认分支应该是 beta,如今咱们须要切换到 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"

  2. 执行终端命令,建立你的 Flutter Library:flutter create -t module flutter_library

    等待执行,建立成功后,会以下所示:

    这里写图片描述
    注意:命令中的 flutter_library, 是我对 Flutter Library 的命名。你能够替换为你的命名。

  3. 将 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() {}

  @override
  void 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

喜欢的同窗能够点点 star ~~~

相关文章
相关标签/搜索