本文是給第一次接觸flutter的原生開發iOS/android同窗快速入門的攻略,高手請繞路,輕拍哈。前端
對於原生開發的同窗,對於flutter會比較感興趣,也許會從網上零星得到一些學習資源,可是比較零散,不構成學習路徑,可能也會踩一些坑,爲了不少走彎路,又能快速的入門flutter,現將我的的一些實踐經歷分享一下,供你們批評指正補充。android
1.1 先下載flutter編輯器 android studio,最新版本,解壓,安裝。。。(如下簡稱AS)ios
1.2 安裝flutter 已iOS爲例git
cd ~/development unzip ~/Downloads/flutter_macos_v1.9.1+hotfix.2-stable.zip 複製代碼
先編輯bash_profile文件(默認狀況下,macOS Mojave(及更早版本)使用Bash shell,所以編輯$HOME/.bashrc)github
$vim ~/.bash_profile 複製代碼
添加如下路徑shell
export PATH="$PATH:`pwd`/flutter/bin"
macos
完整的以下編程
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" export PATH="/Users/boob/Documents/flutter/bin:$PATH" export PATH="/usr/local/opt/openssl/bin:$PATH" export PATH="/Users/boob/Documents/depot_tools:$PATH" export PATH="/Users/boob/Library/Android/sdk/platform-tools:$PATH" export PATH 複製代碼
編輯完成使用wq退出,便可執行flutter命令了swift
flutter --version
看看版本號, which flutter
能夠查看flutter安裝的路徑ios的同窗默認安裝了xcode,沒有的話去安裝一個吧,安卓同窗須要下載android studio,後續開發都是須要用as進行開發和調試的。 爲了第一次能直接運行flutter,咱們先開一個模擬器,而且把其餘真機設備移除,防止後面操做找不到運行目標,或不知道如何選擇設備vim
前提操做 打開模擬器命令
open -a Simulator 複製代碼
檢測pod的版本號是否高於1.6.0
pod --version
複製代碼
flutter默認最低支持的pod版本是1.6.0,若是使用到plugin時就會提示版本太低,致使pod失敗了
flutter create testflutter
複製代碼
注意工程名字都要小寫,不然會提示你命名出錯。
$ cd myapp $ flutter run 複製代碼
此時flutter會編譯dart代碼,而且簽名運行
咱們進入到flutter源碼目錄
ios目錄存放ios工程,android則存放android工程 對於ios來講,編譯的產物在ios/Flutter文件夾中 包含了
App.framework 這是flutter工程編譯出來的ios產物,對於debug編譯來講,flutter_assets包含了全部的可執行產物和資源 對於release編譯來講,可執行的部分在APP文件中,資源存放在flutter_assets中
flutter.framework 俗稱flutter engine/flutter 引擎,支持上層flutter運行的底層庫。
xxx.xcconfig 工程配置,flutter命令自動生成的,爲了配置flutter路徑,flutterframework的路徑
flutter_export_environment.sh 1.9新增的腳本,配置flutter經常使用的環境變量
這是flutter的吹噓的幾大特性之一,跨平臺一致性,熱重載。。。 即寫完代碼能夠當即執行。 編寫flutter代碼咱們使用android studio,官方推薦3.0以上的版本 developer.android.com/studio
咱們可使用最新的,由於已經使用了最新的flutter插件功能,包括斷點調試,attach,性能查看分析等。
用AS打開testflutter工程
找到lib路徑,這是咱們dart代碼存放的路徑,flutter項目是用dart語言開發。如今能夠點運行按鈕▶
️,直接啓動flutter,這個跟在終端啓動效果同樣。
修改一下源碼,把標題改爲個人第一個flutter項目
,以下
而後按下 cmd+s 保存,便可在模擬器上看到運行結果
默認提供兩套ui庫,一套是android風格的Material Design
和ios風格的 cupertino (連接是傳送門)
下面感覺一下差異
咱們使用一個button試一下 在main.dart的scaffold的中添加代碼
CupertinoButton( child: Text('CLICK ME'), color: Theme.of(context).accentColor, onPressed: (){ print("點擊了按鈕"); }, disabledColor: Theme.of(context).disabledColor, ) 複製代碼
到了源碼級別,原生的編程思路就須要開始轉變了,因爲原生開發都是命令式編程,然而前端和flutter是聲明式編程的。
對於命令式,是指若是咱們要對一個文本內容和文本顏色改變,咱們就去取到這個文本的text和textcolor 而後對text和textcolor進行賦值。
然而對於聲明式,要改文本內容,須要將文本的內容text和文本的控件分別先聲明
全部佈局的控件都寫到 Widget build(BuildContext context) { ... }
方法中,可是 可是控件須要用到是內容而且可能改變的,則使用一個變量記下來.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
複製代碼
final String title;
就是聲明瞭一個title的字符串。
或者
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
...
}
複製代碼
AppBar(
title: Text(widget.title),
),
複製代碼
Text( '$_counter', style: Theme.of(context).textTheme.display1, ), 複製代碼
這是和原生最大的差異,須要改變文本的內容則須要使用setState中生效,告訴flutter這時候狀態變化了,須要從新刷新。
例子中單擊+
數字+1,能夠看到界面上的數字當即更新了
void _incrementCounter() { setState(() { _counter++; }); } 複製代碼
值得注意的是,state頻繁刷新也會帶來性能問題,不可濫用哦。 其餘代碼你們能夠自行研究,都是聲明式編程
的運用。
widget在ui層面至關於原生的uiview,可是不只僅侷限於顯示視圖UIView,也有用於佈局相關的。
Container、Button、Row和Column、Text、Scaffold、Icon、Image、Stack、TabBar+TabBarView、Widget-輸入框TextField
Align、Center、Expended、LayoutBuilder、Padding、Wrap
CustomScrollView、GridView、ListView、PageView、SingleChildScrollView
BoxDecoration、Clip系列、Opacity、SafeArea、高斯模糊BackdropFilter
咱們知道橫向佈局使用Row 縱向佈局使用Column Wiget 佈局對其方式分爲主軸和交叉軸,若是是Row佈局主軸mainAxisAlignment就是橫向,而其交叉軸就是縱軸, 主軸排列方式有6中,start,end,center,spaceAround,spaceBetween,spaceEvenly
Padding(
padding: const EdgeInsets.all(0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
)
複製代碼
效果以下
Padding(
padding: const EdgeInsets.all(0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
複製代碼
效果圖以下
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
複製代碼
效果圖以下
start 表示將子級放置在儘量靠近主軸起點的位置。若是此值沿水平方向使用,則[TextDirection]必須爲可用於肯定起點是左側仍是右側。 若是在垂直方向上使用此值,則[VerticalDirection]必須爲可用於肯定起點是頂部仍是底部。
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
複製代碼
效果圖以下:
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
複製代碼
效果以下
Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,),
Container(color: Colors.red, width: 50, height: 50,),
Container(color: Colors.blue, width: 50, height: 50,)
],
),
複製代碼
混合工程方案網上有很多,可是真正的核心只有一個,就是將flutter工程的構建產物加入到原生工程中來。
咱們已經知道了flutter的構建產物有App.framework 其餘的就是Flutter.framework,還有就是dart依賴的第三方庫,多是plugin也多是dart庫,能夠在.symbol文件夾中找到(.xxx開頭的文件默認是隱藏的,使用shift+cmd+. 將其顯示出來)
要想加入到主工程,沒法就是將三個東西作成pod的形式,在pod install/update的時候將他pod進來。這樣就能作原生開發人員無感知的使用flutter工程。
業界有一個主流的方向,安裝產物的本地和遠程來劃分,本地依賴方式和遠程依賴方式。沒法仍是把產物放哪的問題。
提供另外一個視角看混合工程可能更好。咱們按照角色劃分混合方案,最多三種角色
這三種角色的訴求分別是,
原生開發無需安裝flutter,可是會用到flutter的產物。
其餘人員都須要安裝flutter,因此需求差很少。 a、安裝了flutter的同窗,可能也不想編譯flutter產物而是直接使用,b、flutter開發的同窗可能只想編譯debug的產物,可是不想上傳,他們想debug構建並attach到 c、對於構建的需求是須要使用最新的代碼構建release的包。
使用官方的混合方式沒法解決以上全部的需求。本身開發混合工程腳本,須要從以上角度考慮。
然而使用方法僅僅是簡單的一句話
flutter_application_path = 'xxxflutter' load File.join(flutter_application_path, 'IOSFlutterConfig', 'start.rb') def GirFlutter puts "自動檢測flutter是否存在,自動執行不一樣的腳本" install_flutter_engine_pod end 複製代碼
以後pod update
便可!
分爲兩個部分,flutter調用native的代碼、native調用flutter的功能 官方提供了兩種方式 methodchannel、eventchannel。
流程圖以下,
另外plugin就是一種native和flutter通訊的最好的例子,咱們能夠直接看他給的例子。
終端執行
flutter create --org com.example --template=plugin myplugin
複製代碼
進入myplugin/ios/Classes目錄 咱們看到 SwiftMypluginPlugin.swift
import Flutter import UIKit public class SwiftMypluginPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "myplugin", binaryMessenger: registrar.messenger()) let instance = SwiftMypluginPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { result("iOS " + UIDevice.current.systemVersion) } } 複製代碼
能夠看到這個插件建立了一個名爲myplugin的FlutterMethodChannel,而且經過registar註冊到了methodcalldelegate裏面了。
在dart那邊可使用調起該方法。
class Myplugin { static const MethodChannel _channel = const MethodChannel('myplugin'); static Future<String> get platformVersion async { final String version = await _channel.invokeMethod('getPlatformVersion'); return version; } } 複製代碼
那麼對於native如何調用flutter的代碼呢 在methodchannel這個例子中,咱們看到已經給到了一個result過來了,咱們能夠拿到這個result result: @escaping FlutterResult
,若是有新的事件想發出去,就不斷回調這個result也能夠。
self.result("第二次回調給flutter") 複製代碼
使用流程相似,先註冊-> 發送事件
FlutterEventChannel *evenChannal = [FlutterEventChannel eventChannelWithName:@"flutter_hummer_event" binaryMessenger:[registrar messenger]]; FlutterEventChannelHandel *handle = [FlutterEventChannelHandel new]; instance.eventHandel = handle; [evenChannal setStreamHandler:handle]; 。。。 self.eventHandel.eventsBlock(dic); 複製代碼
dart層使用方式
先註冊一個通知--> 監聽回調
// 註冊一個通知 static const EventChannel eventChannel = const EventChannel('flutter_hummer_event'); eventChannel .receiveBroadcastStream(12345) .listen(_onEvent, onError: _onError); // 回調事件 void _onEvent(Object event) { ... } // 錯誤返回 void _onError(Object error) {} 複製代碼
!! 提示,plugin默認生成swift版的plugin,若是想要選擇ios可使用-i objc選項,其餘配置選項能夠-h查詢
flutter create --org com.example --template=plugin -i objc myplugin22
複製代碼
歡迎你們批評指正補充,爭取作最好最精的入門教程,持續更新中...
原創不易,如需轉載請註明來源,共田君