通過學習本片文章中的知識點,你可以避免掉很多坑,從而輕鬆的實現 Flutter 在 Android 專案中的整合。
簡介
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
2. MVP
在這裡,MVP 就不再贅述,在我的上一篇文章,已經詳細介紹過了。
demo 裡的是 Kotlin 版,但實現原理都是一樣的。
有興趣的點下方連結:
從 0 到 1,帶你解剖 MVP 的神祕之處,並自己動手實現 MVP !
3. Flutter
Flutter,由 Google 在 2018. 02 推出的移動UI框架,
可以快速在 Android 和 iOS 上構建高質量的原生使用者介面。
Flutter 的優勢,在這裡我也不再多說了。在 Flutter 中文網 都是有的。
優勢有很多,當然劣勢也很多!雖說跨平臺,但是對於適配問題,還需要去優化並解決。
效能相關,經常會出現一些卡頓現象,並且對於動畫的實現效果,也不是那麼的理想。
當然,還有很多其他的問題。畢竟現在釋出的也只是 beta 版,上述的這些問題,也會得到很好的解決的。
ok,下面切入正題,我們如何在專案中,去使用 Flutter。
疑問
在 Android 原有專案的基礎,去整合並使用 Flutter,肯定會有下面幾個疑問?
-
如何在原生上,展示 Flutter 介面?
-
原生如何給 Flutter 傳送資料?Flutter 如何接收?
-
Flutter 如何呼叫原生的 method ?通過什麼來呼叫?
-
我們知道在 Flutter 中,主入口只有一個
void main()
,如果在原生介面 A,要顯示一個
ListView
。在原生介面 B,要顯示一個webView
。那我們在 Flutter 中,通過什麼來判斷我要載入的是
ListView
還是webView
呢?
實現
ps:如果電腦前的同學沒有安裝 Flutter,建議先安裝。
1. 在 Android 原生的專案基礎中,如何整合 Flutter
-
開啟你的專案,找到
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"
。 -
執行終端命令,建立你的 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() {}
@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 地址:
喜歡的同學可以點點 star ~~~