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 ~~~

相關文章
相關標籤/搜索