Flutter入坑分享
原文鏈接: http://blog.myweb.kim/flutter/Flutter%E5%85%A5%E5%9D%91%E5%88%86%E4%BA%AB/
簡介
Flutter 是 Google 推出并開源的移動端開發框架(基于「Dart」語言)。使用 Flutter 開發的APP可以同時運行在 IOS 與 Android 平臺上。并且 Flutter 默認帶有 Material 風格 與 Cupertino 風格的主題包(前者Android,后者IOS),可以快速開發一個IOS 風格或者 Android 風格的…Demo...
- 跨平臺
Flutter 不使用 WebView 也不使用操作系統的原生控件,而是自己有用一個 高性能 的渲染引擎,可以非常高效的進行組件繪制UI渲染。這樣 Flutter 可以保證在 IOS 與 Android 上的UI表現一致性 ,開發者無需過多關注平臺差異性上的問題。對于初創公司來說,前期節約開發成本就是最好的融資。。。
-
高性能
與 React Native (以下簡稱RN)的跨平臺不同的是,RN是會將JS編寫的對應組件轉換為原生組件去渲染,而 Flutter 是基于最底層 Skia 的圖形庫去渲染(我覺得有點類似于 DOM 中的 canvas , 從平臺上得到一個畫布,自己在畫布上去渲染),所有的渲染都有 Skia 來完成。
Skia 延伸...Flutter使用Skia作為其2D渲染引擎,Skia是Google的一個2D圖形處理函數庫,包含字型、坐標轉換,以及點陣圖都有高效能且簡潔的表現,Skia是跨平臺的,并提供了非常友好的API,目前Google Chrome瀏覽器和Android均采用Skia作為其繪圖引擎,值得一提的是,由于Android系統已經內置了Skia,所以Flutter在打包APK(Android應用安裝包)時,不需要再將Skia打入APK中,但iOS系統并未內置Skia,所以構建iPA時,也必須將Skia一起打包,這也是為什么Flutter APP的Android安裝包比iOS安裝包小的主要原因。
正是因為基于自己的渲染機制,不需要與原生平臺之間頻繁通信,才體現出來他的高效率、高性能。Flutter 的布局、渲染都是 Dart 直接控制,在一些交互中,比如滑動的時候它的高性能就會體現出來。而RN在這方面的渲染則是與原生平臺進行通信,不斷的進行信息同步,這部分的開銷放到手機上還是很大的。
而且在渲染層,Flutter 底層也有一個類似虛擬DOM的組件,在UI進行變化后,會進行diff算法。
- 開發高效率
Flutter 在開發的時候有一個特點,熱重載。 就像在webpack 與 瀏覽器,在編輯器中保存后,界面立馬就能看到變化。Flutter 也是這樣,當將 APP 在虛擬容器中或者真機設備中調試時,保存后,APP會立刻響應。節省了大量時間。
Dart 初步了解
因為 Flutter 是基于 Dart 語言開發的,所以我們多多少少也要了解下 Dart 這玩意怎么寫,他的語法與結構是個怎樣的。雖然官網的 Demo 有提到說:「如果您熟悉面向對象和基本編程概念(如變量、循環和條件控制),則可以完成本教程,您無需要了解Dart或擁有移動開發的經驗?!筫mmmm... 純屬扯淡...
如果不了解 Dart,那也僅限于看 Demo 是怎么寫的...
Dart 出自Google。是一種面向對象編程的強類型語言,語法有點像 Java 與 JavaScript 的集合體。
官方學習資料
以下是使用 Flutter 需要掌握的 Dart 基礎語法:
(以下內容摘抄來至 官網文檔 , 沒必要細看,可快速的過一遍,只做了解。)
變量聲明
var
類似于JavaScript中的var,它可以接收任何類型的變量,但最大的不同是Dart中var變量一旦賦值,類型便會確定,則不能再改變其類型,如:
var t; t="hi world"; // 下面代碼在dart中會報錯,應為變量t的類型已經確定為String, // 類型一旦確定后則不能再更改其類型。 t=1000;上面的代碼在JavaScript是沒有問題的,前端開發者需要注意一下,之所以有此差異是因為Dart本身是一個強類型語言,任何變量都是有確定類型的,在Dart中,當用var聲明一個變量后,Dart在編譯時會根據第一次賦值數據的類型來推斷其類型,編譯結束后其類型就已經被確定,而JavaScript是純粹的弱類型腳本語言,var只是變量的聲明方式而已。
dynamic和Object
Dynamic和Object 與 var功能相似,都會在賦值時自動進行類型推斷,不同在于,賦值后可以改變其類型,如:
dynamic t; t="hi world"; //下面代碼沒有問題 t=1000;Object 是dart所有對象的根基類,也就是說所有類型都是Object的子類,所以任何類型的數據都可以賦值給Object聲明的對象,所以表現效果和dynamic相似。
final和const
如果您從未打算更改一個變量,那么使用 final 或 const,不是var,也不是一個類型。 一個 final 變量只能被設置一次,兩者區別在于:const 變量是一個編譯時常量,final變量在第一次使用時被初始化。被final或者const修飾的變量,變量類型可以省略,如:
//可以省略String這個類型聲明 final str = "hi world"; //final str = "hi world"; const str1 = "hi world"; //const String str1 = "hi world";函數
Dart是一種真正的面向對象的語言,所以即使是函數也是對象,并且有一個類型Function。這意味著函數可以賦值給變量或作為參數傳遞給其他函數,這是函數式編程的典型特征。
函數聲明
bool isNoble(int atomicNumber) {return _nobleGases[atomicNumber] != null; }dart函數聲明如果沒有顯示申明返回值類型時會默認當做dynamic處理,注意,函數返回值沒有類型推斷:
typedef bool CALLBACK();//不指定返回類型,此時默認為dynamic,不是bool isNoble(int atomicNumber) {return _nobleGases[atomicNumber] != null; }void test(CALLBACK cb){print(cb()); } //報錯,isNoble不是bool類型 test(isNoble);對于只包含一個表達式的函數,可以使用簡寫語法
bool isNoble (int atomicNumber )=> _nobleGases [ atomicNumber ] != null ;函數作為變量
var say= (str){print(str); }; say("hi world");函數作為參數傳遞
void execute(var callback){callback(); } execute(()=>print("xxx"))可選的位置參數
包裝一組函數參數,用[]標記為可選的位置參數:
String say(String from, String msg, [String device]) {var result = '$from says $msg';if (device != null) {result = '$result with a $device';}return result; }下面是一個不帶可選參數調用這個函數的例子:
say('Bob', 'Howdy'); //結果是: Bob says Howdy下面是用第三個參數調用這個函數的例子:
say('Bob', 'Howdy', 'smoke signal'); //結果是:Bob says Howdy with a smoke signal可選的命名參數
定義函數時,使用{param1, param2, …},用于指定命名參數。例如:
//設置[bold]和[hidden]標志 void enableFlags({bool bold, bool hidden}) {// ... }調用函數時,可以使用指定命名參數。例如:paramName: value
enableFlags(bold: true, hidden: false);可選命名參數在Flutter中使用非常多。
異步支持
Dart類庫有非常多的返回Future或者Stream對象的函數。 這些函數被稱為異步函數:它們只會在設置好一些需要消耗一定時間的操作之后返回,比如像 IO操作。而不是等到這個操作完成。
async和await關鍵詞支持了異步編程,運行您寫出和同步代碼很像的異步代碼。
Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失敗)及其結果值的表示。簡單來說,它就是用于處理異步操作的,異步處理成功了就執行成功的操作,異步處理失敗了就捕獲錯誤或者停止后續操作。一個Future只會對應一個結果,要么成功,要么失敗。
由于本身功能較多,這里我們只介紹其常用的API及特性。還有,請記住,Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調用。
為了方便示例,在本例中我們使用Future.delayed 創建了一個延時任務(實際場景會是一個真正的耗時任務,比如一次網絡請求),即2秒后返回結果字符串"hi world!",然后我們在then中接收異步結果并打印結果,代碼如下:
Future.delayed(new Duration(seconds: 2),(){return "hi world!"; }).then((data){print(data); });如果異步任務發生錯誤,我們可以在catchError中捕獲錯誤,我們將上面示例改為:
Future.delayed(new Duration(seconds: 2),(){//return "hi world!";throw AssertionError("Error"); }).then((data){//執行成功會走到這里 print("success"); }).catchError((e){//執行失敗會走到這里 print(e); });在本示例中,我們在異步任務中拋出了一個異常,then 的回調函數將不會被執行,取而代之的是 catchError回調函數將被調用;但是,并不是只有 catchError回調才能捕獲錯誤,then方法還有一個可選參數onError,我們也可以它來捕獲異常:
Future.delayed(new Duration(seconds: 2), () {//return "hi world!";throw AssertionError("Error"); }).then((data) {print("success"); }, onError: (e) {print(e); });有些時候,我們會遇到無論異步任務執行成功或失敗都需要做一些事的場景,比如在網絡請求前彈出加載對話框,在請求結束后關閉對話框。這種場景,有兩種方法,第一種是分別在then或catch中關閉一下對話框,第二種就是使用Future的whenComplete回調,我們將上面示例改一下:
Future.delayed(new Duration(seconds: 2),(){//return "hi world!";throw AssertionError("Error"); }).then((data){//執行成功會走到這里 print(data); }).catchError((e){//執行失敗會走到這里 print(e); }).whenComplete((){//無論成功或失敗都會走到這里 });有些時候,我們需要等待多個異步任務都執行結束后才進行一些操作,比如我們有一個界面,需要先分別從兩個網絡接口獲取數據,獲取成功后,我們需要將兩個接口數據進行特定的處理后再顯示到UI界面上,應該怎么做?答案是Future.wait,它接受一個Future數組參數,只有數組中所有Future都執行成功后,才會觸發then的成功回調,只要有一個Future執行失敗,就會觸發錯誤回調。下面,我們通過模擬Future.delayed 來模擬兩個數據獲取的異步任務,等兩個異步任務都執行成功時,將兩個異步任務的結果拼接打印出來,代碼如下:
Future.wait([// 2秒后返回結果 Future.delayed(new Duration(seconds: 2), () {return "hello";}),// 4秒后返回結果 Future.delayed(new Duration(seconds: 4), () {return " world";}) ]).then((results){print(results[0]+results[1]); }).catchError((e){print(e); });執行上面代碼,4秒后你會在控制臺中看到“hello world”。
Async/await
Dart中的async/await 和JavaScript中的async/await功能和用法是一模一樣的,如果你已經了解JavaScript中的async/await的用法,可以直接跳過本節。
如果代碼中有大量異步邏輯,并且出現大量異步任務依賴其它異步任務的結果時,必然會出現Future.then回調中套回調情況。舉個例子,比如現在有個需求場景是用戶先登錄,登錄成功后會獲得用戶Id,然后通過用戶Id,再去請求用戶個人信息,獲取到用戶個人信息后,為了使用方便,我們需要將其緩存在本地文件系統,代碼如下:
//先分別定義各個異步任務 Future<String> login(String userName, String pwd){...//用戶登錄 }; Future<String> getUserInfo(String id){...//獲取用戶信息 }; Future saveUserInfo(String userInfo){...// 保存用戶信息 };接下來,執行整個任務流:
login("alice","******").then((id){//登錄成功后通過,id獲取用戶信息 getUserInfo(id).then((userInfo){//獲取用戶信息后保存 saveUserInfo(userInfo).then((){//保存用戶信息,接下來執行其它操作...});}); })可以感受一下,如果業務邏輯中有大量異步依賴的情況,將會出現上面這種在回調里面套回調的情況,過多的嵌套會導致的代碼可讀性下降以及出錯率提高,并且非常難維護,這個問題被形象的稱為回調地獄(Callback hell)?;卣{地獄問題在之前JavaScript中非常突出,也是JavaScript被吐槽最多的點,但隨著ECMAScript6和ECMAScript7標準發布后,這個問題得到了非常好的解決,而解決回調地獄的兩大神器正是ECMAScript6引入了Promise,以及ECMAScript7中引入的async/await。 而在Dart中幾乎是完全平移了JavaScript中的這兩者:Future相當于Promise,而async/await連名字都沒改。接下來我們看看通過Future和async/await如何消除上面示例中的嵌套問題。
正如上文所述, “Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調用” ,如果在then中返回的是一個Future的話,該future會執行,執行結束后會觸發后面的then回調,這樣依次向下,就避免了層層嵌套。
通過Future回調中再返回Future的方式雖然能避免層層嵌套,但是還是有一層回調,有沒有一種方式能夠讓我們可以像寫同步代碼那樣來執行異步任務而不使用回調的方式?答案是肯定的,這就要使用async/await了,下面我們先直接看代碼,然后再解釋,代碼如下:
task() async {try{String id = await login("alice","******");String userInfo = await getUserInfo(id);await saveUserInfo(userInfo);//執行接下來的操作 } catch(e){//錯誤處理 print(e); } }- async用來表示函數是異步的,定義的函數會返回一個Future對象,可以使用then方法添加回調函數。
- await 后面是一個Future,表示等待該異步任務完成,異步完成后才會往下走;await必須出現在 async 函數內部。
可以看到,我們通過async/await將一個異步流用同步的代碼表示出來了。
其實,無論是在JavaScript還是Dart中,async/await都只是一個語法糖,編譯器或解釋器最終都會將其轉化為一個Promise(Future)的調用鏈。Stream
Stream 也是用于接收異步事件數據,和Future 不同的是,它可以接收多個異步操作的結果(成功或失敗)。 也就是說,在執行異步任務時,可以通過多次觸發成功或失敗事件而傳遞結果數據或錯誤異常。 Stream 常用于會多次讀取數據的異步任務場景,如網絡內容下載、文件讀寫等。舉個例子:
Stream.fromFutures([// 1秒后返回結果Future.delayed(new Duration(seconds: 1), () {return "hello 1";}),// 拋出一個異常Future.delayed(new Duration(seconds: 2),(){throw AssertionError("Error");}),// 3秒后返回結果Future.delayed(new Duration(seconds: 3), () {return "hello 3";}) ]).listen((data){print(data); }, onError: (e){print(e.message); },onDone: (){});上面的代碼依次會輸出:
I/flutter (17666): hello 1 I/flutter (17666): Error I/flutter (17666): hello 3代碼很簡單,就不贅述了。
思考題:既然Stream可以接收多次事件,那能不能用Stream來實現一個訂閱者模式的事件總線?總結
通過上面介紹,相信你對Dart應該有了一個初步的印象,由于筆者平時也使用Java和JavaScript,下面筆者根據自己的經驗,結合Java和JavaScript,談一下自己的看法。
之所以將Dart與Java和JavaScript對比,是因為,這兩者分別是強類型語言和弱類型語言的典型代表,并且Dart 語法中很多地方也都借鑒了Java和JavaScript。Dart vs Java
客觀的來講,Dart在語法層面確實比Java更有表現力;在VM層面,Dart VM在內存回收和吞吐量都進行了反復的優化,但具體的性能對比,筆者沒有找到相關測試數據,但在筆者看來,只要Dart語言能流行,VM的性能就不用擔心,畢竟Google在go(沒用vm但有GC)、javascript(v8)、dalvik(android上的java vm)上已經有了很多技術積淀。值得注意的是Dart在Flutter中已經可以將GC做到10ms以內,所以Dart和Java相比,決勝因素并不會是在性能方面。而在語法層面,Dart要比java更有表現力,最重要的是Dart對函數式編程支持要遠強于Java(目前只停留在lamda表達式),而Dart目前真正的不足是生態,但筆者相信,隨著Futter的逐漸火熱,會回過頭來反推Dart生態加速發展,對于Dart來說,現在需要的是時間。
Dart vs JavaScript
JavaScript的弱類型一直被抓短,所以TypeScript、Coffeescript甚至是Facebook的flow(雖然并不能算JavaScript的一個超集,但也通過標注和打包工具提供了靜態類型檢查)才有市場。就筆者使用過的腳本語言中(筆者曾使用過Python、PHP),JavaScript無疑是動態化支持最好的腳本語言,比如在JavaScript中,可以給任何對象在任何時候動態擴展屬性,對于精通JavaScript的高手來說,這無疑是一把利劍。但是,任何事物都有兩面性,JavaScript的強大的動態化特性也是把雙刃劍,你可經常聽到另一個聲音,認為JavaScript的這種動態性糟糕透了,太過靈活反而導致代碼很難預期,無法限制不被期望的修改。畢竟有些人總是對自己或別人寫的代碼不放心,他們希望能夠讓代碼變得可控,并期望有一套靜態類型檢查系統來幫助自己減少錯誤。正因如此,在Flutter中,Dart幾乎放棄了腳本語言動態化的特性,如不支持反射、也不支持動態創建函數等。并且Dart在2.0強制開啟了類型檢查(Strong Mode),原先的檢查模式(checked mode)和可選類型(optional type)將淡出,所以在類型安全這個層面來說,Dart和TypeScript、Coffeescript是差不多的,所以單從這一點來看,Dart并不具備什么明顯優勢,但綜合起來看,dart既能進行服務端腳本、APP開發、web開發,這就有優勢了!
官方PPT宣傳截圖
Flutter 底層架構的一個大概示意圖:
Material 和 Cupertino 是 Flutter 官方提供的兩個不同的 UI 風格組件庫(前者Android,后者IOS)。
在 Flutter 中,一切皆是 Widget 。 一個按鈕是 Widget,一段文字也是 Widget,一個圖片也是 Widget,一個路由導航 也是 Widget。所以前期接觸 Flutter 可以先學習這兩個UI庫如何使用即可。(個人見解)
基礎組件庫
Material 組件庫
Cupertino 組件庫
搭建開發環境
- windows上的搭建
- macOS上的搭建
- linux上的搭建
搭建過程很簡單,下載 SDK 包,然后配置下環境變量就ok了。
編輯器推薦
VScode,輕巧、簡潔。
配置好 Flutter環境,只需要在安裝一個 Flutter 插件就好了。
官方配置教程
第一個Demo
在 VScode 中安裝好插件后,按下shift + command + p 輸入 flutter ,選擇 New Project。
第一次創建時可能需要選擇 Flutter SDK 的位置。
下面的Demo是官網上的給出的代碼,整理出來的一個完整的。
先在 pubspec.yaml 中添加一個依賴: english_words 它是 Dart 語言編寫的一個隨機生成英文單詞的工具包。
pubspec.yaml 是 Flutter 配置文件,可以理解為 npm 中的 package.json找到文件的第21行:
dependencies:flutter:sdk: flutter# The following adds the Cupertino Icons font to your application.# Use with the CupertinoIcons class for iOS style icons.cupertino_icons: ^0.1.2# 在這里添加 版本號遵循 語義化(Semantic Versioning)english_words: ^3.1.5dev_dependencies:flutter_test:sdk: flutter Flutter 有一個官方的包管理平臺,pub.dartlang.org 類似于npm添加完成后,在控制臺輸入flutter packages get 或者在編輯器中右鍵點擊 pubspes.yaml 選擇 Get Packages
也就是安裝新的依賴。
這個Demo是一個隨機生成英文名字的程序,有一個可以無限滾動的列表,可以讓用戶對喜歡的名字進行紅心標記搜藏,然后點擊右上角,可以查看已收藏的名字(路由跳轉來實現的)。
將lib/main.dart 中的所有代碼刪除,替換成下面的代碼:
運行成功后如下圖所示:
官方學習資料鏈接
Flutter 中文網
Flutter 實戰
以上,致那顆騷動的心……
總結
以上是生活随笔為你收集整理的Flutter入坑分享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS---内外边距
- 下一篇: Java建造者模式