學習Flutter也有一段時間了,今天來介紹一下Flutter是如何與原生互動的。
原生互動的重要性就不用說了吧。畢竟Flutter也不是萬能的,有時候還是需要我們們原生的支援,才能達成各種奇奇怪怪的需求,那麼話不多說,直接開幹。
1. 新建一個Flutter工程
這次我們的目的是與原生互動,那麼建立方式自然與先前不同
之前選擇的是 Flutter Application
普通工程
這次我們選擇 Flutter Module
互動工程
從上圖可以看出,剛建立出來的工程,與普通的Flutter Application
不同 android
與 ios
資料夾的名稱前面都多了個.
,在本地資料夾中檢視帶 .
的資料夾可以發現這兩個資料夾是隱藏資料夾。
那麼為什麼要把這兩個資料夾隱藏起來呢?
答:這兩個資料夾的內容與普通的Flutter Application
一樣,但是這兩個工程只是用來給測試的,不參與到原生互動當中。
所謂的測試就是我們在Android Studio中Run一個專案。
2. Xcode新建一個Native工程
建立一個Xcode專案與Flutter專案同一路徑,如下圖
3. 新增依賴
3.1 cocoapods 引入Flutter支援
- $cd Native工程路徑
- $pod init
- 編輯 podFile 內容 (內容在下面,注意看中文內容)
- pod install
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'FlutterNative' do
flutter_application_path = '../你的flutter資料夾/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
end
複製程式碼
3.2 編譯Native內容
- 開啟Native資料夾中的
.xcworkspace
- 關閉Bitcode
- 編譯一下工程,一般情況都會
Build Success
3.3 設定Flutter編譯指令碼
- TARGET -> Build Phases -> 新增指令碼
- 輸入指令碼內容***(內容如下)***
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
複製程式碼
指令碼內容可以在對應路徑下找到,有興趣的同學可以自行翻閱。
-
移動指令碼編譯位置 因為
Build Success
是有編譯順序的,為了避免一些不必要的情況。 按下圖操作。 -
command + B 跑一下
3.4 寫程式碼
- 我在
Main.storyboard
中建立了個按鈕,並將其點選事件拖到ViewController.m
中。 - 宣告瞭一個FlutterViewController屬性變數
- 在點選事件中,present這個VC
tips: FlutterViewController無需重複建立,一旦載入之後,在程式執行期間將永!不!釋!放!,重複建立將會導致記憶體佔用越來越大。
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@property(strong,nonatomic)FlutterViewController *flutterVC;
@end
@implementation ViewController
- (IBAction)FirstBtnClick:(id)sender {
self.flutterVC = [FlutterViewController new];
[self presentViewController:self.flutterVC animated:YES completion:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
複製程式碼
#####3.5 執行程式(Xcode工程)
-
看到我們建立好的Flutter工程最初的樣子
-
Flutter中嘗試修改一下標題,重新執行Xcode工程
至此,我們就完成了Flutter與Native原生互動的第一步!搭建好Flutter 與Native 原生之間的一道橋。
###4. 開始互動 我們開始在上文程式碼的基礎上,繼續編寫互動程式碼。 #####一、設定預設初始化路由頁面 1. Xcode工程設定初始化路由
- (IBAction)FirstBtnClick:(id)sender {
self.flutterVC = [FlutterViewController new];
//設定初始化路由
[_flutterVC setInitialRoute:@"pageID"];
[self presentViewController:self.flutterVC animated:YES completion:nil];
}
複製程式碼
2. Flutter工程中設定預設路由名稱
2.1
import 'dart:ui';
2.2. 宣告一個變數,接收Native傳送過來的字串final String pageIdentifier
; 2.3 Flutter列印接收到的內容
import 'package:flutter/material.dart';
import 'dart:ui';
//傳入window.defaultRouteName
void main() => runApp(MyApp(pageIdentifier: window.defaultRouteName,));
class MyApp extends StatefulWidget {
//宣告接收變數
final String pageIdentifier;
const MyApp({Key key, this.pageIdentifier}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
//列印widget.pageIdentifier
print(widget.pageIdentifier);
return MaterialApp(
home: Container(
color: Colors.white,
child: Center(
child: Text(widget.pageIdentifier),
) ,
)
);
}
}
複製程式碼
效果如下:
3. 配置不同ID做不同的事
在Native端配置不同的setInitialRoute
,然後在Flutter端接收到之後,根據不同的ID,顯示不同的頁面。
示例程式碼如下:
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
print(widget.pageIdentifier);
switch(widget.pageIdentifier){
case 'pageA':{
return PageA();
}
case 'pageB':{
return PageB();
}
case 'pageC':{
return PageC();
}
defult:{
return DefalutPage();
}
}
}
}
複製程式碼
警告:預設初始路由只能設定一次!後面反覆設定時,不論傳遞的是什麼,預設路由都是第一次進入時的那個。 原因是因為我們Flutter中,是用一個final修飾符修飾的變數接收,所以如果想換初始路由,我們需要重新建立一個FlutterViewController,但是這又是耗費記憶體。 所以,如果要跳轉不同介面,還請繼續往下看。
#####二、 Flutter傳遞資料給Native
這裡我們建立在上文內容的基礎上,給PageID加上一個點選事件,在點選PageID之後,退出Flutter介面,回到Native介面。
1. 引入服務
import 'package:flutter/services.dart';
2. MethodChannel
2.1 Flutter點選事件使用MethodChannel傳遞資料
child: GestureDetector(
onTap: () {
MethodChannel('test').invokeListMethod('dismiss','這裡寫引數');
},
child: Text(widget.pageIdentifier),
),
複製程式碼
2.2 Native建立FlutterMethodChannel並設定回撥
self.flutterVC = [FlutterViewController new];
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"test" binaryMessenger:self.flutterVC];
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"%@ -- %@",call.method,call.arguments);
if ([call.method isEqualToString:@"dismiss"]) {
[self.flutterVC dismissViewControllerAnimated:YES completion:nil];
}
}];
複製程式碼
2.3 測試通道是否連通
如上,我們已經成功獲取了Flutter端傳遞給Native端的資料。 接下來,我們再試試Native如何通過MethodChannel傳遞引數給Flutter。
#####三、Native傳遞資料給Flutter
這裡我們需要修改的內容比較多,請耐心看。
- Flutter建立MethodChannel
final MethodChannel _channerOne = MethodChannel('pageOne');
final MethodChannel _channerTwo = MethodChannel('pageTwo');
final MethodChannel _channerDefault = MethodChannel('pageDefault');
複製程式碼
- 設定變數獲取介面名稱以及初始化判斷
var _pageName = '';
var _initialized = false;
複製程式碼
- 通道設定通道回撥
@override
void initState() {
// TODO: implement initState
super.initState();
_channerOne.setMethodCallHandler((MethodCall call){
print('這是One接收原生的回撥${call.method}==${call.arguments}');
_pageName = call.method;
setState(() {});
});
_channerTwo.setMethodCallHandler((MethodCall call){
print('這是Two接收原生的回撥${call.method}==${call.arguments}');
_pageName = call.method;
setState(() {});
});
_channerDefault.setMethodCallHandler((MethodCall call){
print('這是Default接收原生的回撥${call.method}==${call.arguments}');
_pageName = call.method;
setState(() {});
});
}
複製程式碼
4.設定build方法
@override
Widget build(BuildContext context) {
//如果還沒初始化那麼判斷規則就用初始化時傳進來的ID
String switchValue = _initialized?_pageName:widget.pageIdentifier;
//標記已經初始化
_initialized = true;
switch(switchValue){
case 'pageOne':{
return MaterialApp(
home: Container(
color: Colors.white,
child: Center(
child: GestureDetector(
onTap: () {
_channerOne.invokeListMethod('dismissOne','這是通道一傳回來的資料');
},
child: Text('這是第一頁'),
),
) ,
)
);
}
case 'pageTwo':{
return MaterialApp(
home: Container(
color: Colors.white,
child: Center(
child: GestureDetector(
onTap: () {
_channerTwo.invokeListMethod('dismissTwo','這是通道二傳回來的資料');
},
child: Text('這是第二頁'),
),
) ,
)
);
}
default:{
return MaterialApp(
home: Container(
color: Colors.white,
child: Center(
child: GestureDetector(
onTap: () {
_channerDefault.invokeListMethod('dismissDefault','這是預設通道傳回來的資料');
},
child: Text('這是預設'),
),
) ,
)
);
}
}
}
複製程式碼
- 回到Native端 請注意看註釋
@interface ViewController ()
{
//標記是否已經初始化過
BOOL isSettedInitialRoute;
}
@property(strong,nonatomic)FlutterViewController *flutterVC;
@property(strong,nonatomic)FlutterMethodChannel *channelOne;
@property(strong,nonatomic)FlutterMethodChannel *channelTwo;
@property(strong,nonatomic)FlutterMethodChannel *channelDefault;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//建立FlutterViewController(還沒初始化,只有進入到Flutter頁面後才算初始化完成)
self.flutterVC = [FlutterViewController new];
//初始化通道一
self.channelOne = [FlutterMethodChannel methodChannelWithName:@"pageOne" binaryMessenger:self.flutterVC];
__weak typeof(self) weakself = self;
//註冊通道一回撥
[_channelOne setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"method:%@ -- arguments:%@",call.method,call.arguments);
if ([call.method isEqualToString:@"dismissOne"]) {
[weakself.flutterVC dismissViewControllerAnimated:YES completion:nil];
}
}];
//初始化通道二
self.channelTwo = [FlutterMethodChannel methodChannelWithName:@"pageTwo" binaryMessenger:self.flutterVC];
//註冊通道二回撥
[_channelTwo setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"method:%@ -- arguments:%@",call.method,call.arguments);
if ([call.method isEqualToString:@"dismissTwo"]) {
[weakself.flutterVC dismissViewControllerAnimated:YES completion:nil];
}
}];
//初始化預設通道
self.channelDefault = [FlutterMethodChannel methodChannelWithName:@"pageDefault" binaryMessenger:self.flutterVC];
//註冊預設通道回撥
[_channelDefault setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
NSLog(@"method:%@ -- arguments:%@",call.method,call.arguments);
if ([call.method isEqualToString:@"dismissDefault"]) {
[weakself.flutterVC dismissViewControllerAnimated:YES completion:nil];
}
}];
}
- (IBAction)FirstBtnClick:(id)sender {
//如果還沒初始化
if (!isSettedInitialRoute) {
//設定初始介面
[self.flutterVC setInitialRoute: @"pageOne"];
//標記已經初始化
isSettedInitialRoute = YES;
}else{
//如果已經初始化過了,就直接呼叫Flutter中註冊的方法,
[self.channelOne invokeMethod:@"pageOne" arguments:@"iOS通過通道一發訊息給Flutter"];
}
[self presentViewController:self.flutterVC animated:YES completion:nil];
}
- (IBAction)SecondBtnClick:(id)sender {
//這裡就跟FirstBtnClick同理了
if (!isSettedInitialRoute) {
[self.flutterVC setInitialRoute: @"pageTwo"];
isSettedInitialRoute = YES;
}else{
[self.channelTwo invokeMethod:@"pageTwo" arguments:@"iOS通過通道二發訊息給Flutter"];
}
[self presentViewController:self.flutterVC animated:YES completion:nil];
}
複製程式碼
- 互動效果
上文我們利用MethodChannel在Flutter 與 Native 原生之間互動的內容,接下來我們繼續瞭解一下另外一種Channel。
#####BasicMessageChannel 從字面意思呢,我們可以理解為這是一條為傳送基礎訊息資料的通道。 他與MethodChannel有一些區別的地方。接下來就簡單介紹一下這個BasicMessageChannel
1. Flutter端
1.1 建立通道
這裡眼尖的同學會發現,這條通道比MethodChannel多了一個codec
引數,這個引數可以理解成"解碼器"。這裡我們用StandardMessageCodec()
final BasicMessageChannel _basicMessageChannel = BasicMessageChannel('basic', StandardMessageCodec());
複製程式碼
1.2 註冊方法回撥
@override
void initState() {
// TODO: implement initState
super.initState();
_basicMessageChannel.setMessageHandler((message){
print('收到Xcode發來的訊息 == $message');
});
}
複製程式碼
1.3 呼叫訊息通道給Native傳送訊息 這裡由於程式碼比較長,省略無關緊要的部分
Container(
height: 80,
color: Colors.red,
child: TextField(
onChanged: (value){
// 將TextField文字內容傳送給Native
_basicMessageChannel.send(value);
},
),
),
複製程式碼
2. Native端
2.1 宣告屬性
@property(strong,nonatomic)FlutterBasicMessageChannel *basicChannel;
2.2 初始化訊息通道
self.basicChannel = [FlutterBasicMessageChannel messageChannelWithName:@"basic" binaryMessenger:self.flutterVC];
複製程式碼
2.3 註冊方法回撥
[_basicChannel setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) {
NSLog(@"basicMessage == %@",message);
}];
複製程式碼
2.4 Native給Flutter通過訊息通道發訊息
- (IBAction)BasicClick:(id)sender {
[self.basicChannel sendMessage:@"發一條訊息給fultter"];
[self presentViewController:self.flutterVC animated:YES completion:nil];
}
複製程式碼
3. 互動結果
###結語
那麼以上就是本次 Flutter 與 Native 原生互動的全部內容。當然 這裡還有另外一種互動方式,EventChannel
傳遞資料流,下次有機會的話,在給大家補齊還請見諒。