Flutter是開源并且免費的,擁有現代的響應式框架特性,高速的2D渲染引擎,方便快捷的開發工具以及各種開箱即用的UI組件庫。
Flutter使用Dart作為開發語言,這是一門簡潔、強類型的面向對象的編程語言。為什么Dart是一門適合移動端開發的語言呢?首先是因為它的性能,包括開發和生產環境下的性能,它包含了兩種編譯模式Just-In-Time(即時編譯)和Ahead-Of-Time(運行前編譯)。
JIT即時編譯可以使得Flutter在應用運行時同時執行代碼編譯,這就讓Flutter具備了極速的開發體驗。具備亞秒級的熱重載(Hot Reloading)特性。
AOT運行前編譯意思就是將代碼庫直接編譯成原生的ARM指令集。這樣的方式對于應用來說意味著快速的啟動速度和可預見的卓越性能。
Dart同時具有強類型(static types)和類型推導(sound type)的特性。這樣可以讓Bug暴露于編譯期間,更放心的應付代碼量的增長。
當然,使用Flutter也有別的原因。
設置環境
我們可以通過https://flutter.io/setup](https://flutter.io/setup
來設置自己的開發環境,有一點要注意的是如果大家用的是android studio 那么版本不能低于3.1.X。
如果大家使用的是模擬器進行開發調試的話,理論上可以使用任何一種設備集成任何一種SDK。如果不確定自己該使用哪個系列或版本的話,建議使用Pixel 2 集成最常用的SDK就好了(設置項選擇默認的就好了)。
Flutter
先通過些列子來過一下Flutter的框架(reactor framework)、渲染引擎(rendering engine)和開發工具(development tools)。讓我們對這個事情有一個更為宏觀的認知。
項目創建成功之后你會發現存在一個iOS和Android平臺的文件夾。這是因為Flutter的代碼會編譯成不同平臺的代碼。在新建的項目中,我們只需要把目光專注到lib
下的main.dart
文件就好了。這個文件就是Flutter啟動的入口文件。之后我們再介紹其他文件的作用。。。
Flutter的框架(reactor framework)
前面已經說了,Flutter包含了一個先進的響應式框架和2D的渲染引擎,這里說的響應式框架到底指的是什么呢?一個View被構建為一個由許多個widget組成的不可變(immutable)的樹狀結構。widget是Flutter的基礎單元,widget是用dart編寫的來描述用戶界面的代碼塊。在Flutter的世界里幾乎所有的都是用Dart寫好的widget小部件。
沒有獨立的布局文件來自定義排列和文本對齊等,所有的代碼都是在一個Flutter widget中定義。當一個widget的state發生改變時,比如發生了用戶輸入或者觸發了動畫,widget會根據它當前的最新的state來進行重新繪制。這種方式節省了開發者的時間,因為這種UI可以直接按照某一種state來進行描述。這樣我們就不需要再為widget狀態的改變而去寫多余的代碼了。
Flutter提供了框架來區別出widget所發生的改變,并將這個改變更新到渲染樹上。渲染引擎是我們應用中的一部分,所以我們不需要將UI渲染的代碼和原生平臺做橋接。因為移動應用開發中,布局和渲染的觸發要比調用平臺的攝像頭這種操作更為頻繁。通過在應用層把所有渲染工作完成,Flutter就可以快速渲染并對widget樹重新渲染,讓應用具備更豐富和流暢的動畫效果。這種渲染引擎建立于Skia之上,那么什么又是Skia呢?skia是使用Dart編寫的2D圖形渲染框架,它提供了在iOS和android平臺繪制widget的能力。所以平臺上的系統僅需要提供一個畫布(canvas)就好了,widget的渲染就在Flutter內部的引擎中執行了。AOT模式會編譯成機器指令進而執行。如果任何人希望編寫擴展到其他平臺的應用(非iOS和Android),比如相機和Wi-Fi插件,只要數據可以從操作系統傳給畫布(canvas),那么Flutter應用就有可能編譯到你的目標平臺運行,理論上(我猜測)。
開發工具
這里有幾個名詞,熱重載(Hot reloading)、widget檢查工具(widget inspector)和代碼格式化工具(code auto-formatter)。這幾個工具在開發中真的提高了我們的效率。
Flutter的熱重載工具,能夠讓UI發生變化后就即刻進行重新渲染,這個功能基于即時編譯JIT。而且熱重載工具可以在模擬器和真機上運行。如果我們在開發過程中想讓APP重置,那么我們可以通過點擊綠色的按鈕讓應用直接重啟。那么這個重啟的操作會將應用的state重置。
widget檢查器,有點像Chrome瀏覽器的web檢察器。檢查器可以讓我們在設備上的各個像素之間來回切換,也可以在widget tree中上下切換還可以一行一行的調試在應用里創建的widget。
打開widget檢查器,可以很方便的了解內置widgets的結構。
X的圓形按鈕,這個圖標是inspect的圖標;
有兩個方形的圖標(toggle platform mode),用來在不同設備平臺之間進行切換;
在我們的widget tree中,加粗的widget表示是我們自己創建的,而非內置的widget。如果此時你雙擊選擇某個加粗的widget時,對應的widget代碼就會高亮顯示。在應用的某一處雙擊也可以達到同樣的效果。
在常量前加一個下劃線來表示局部常量(僅在這個文件中生效)
const _padding = EdgeInsets.all(20.0);
最后,說一說代碼格式化,在每一個屬性的后面加上一個逗號,可以被代碼格式化工具(code auto-formatter)檢測到并自動換行。
小部件 Widget
Widgets are the foundation of Flutter apps. A widget is a description of part of a user interface.
嗯嗯,Flutter的世界里一切皆組件,所見之處都是widget。那么它們又被分為Stateless無狀態的和stateful有狀態的兩種。stateless組件是不可變的,這意味著該組件一旦創建那么它的所有屬性都是最終狀態。在widget創建的時候,我們可以傳遞參數,比如背景色。但一旦創建好,這些屬性就是不可改變的了。同樣這類組件也是不可交互的。
另一類呢,stateful widgets 就是可以創建start對象。
那么無狀態的組件是怎么布局的呢?
我們在實例化widget的時候,會傳遞給它一些參數,有些是必須的,有些是可選的。我們可以通過輸入一些屬性值來定制化一個組件的對象。大多數的組件都會有 height、width和child屬性,用來嵌套其他的子組件。child屬性只允許嵌套一個子widget,而children屬性則可以嵌套多個。當然child是可選參數,Flutter允許我們創建不包含子組件的組件。
runApp函數可以接受任意的組件作為參數
MateralApp組件中 debugShowCheckedModeBanner: false -- 快速移除角標
為了讓我們的函數看起來簡潔,我們可以把某些組件在函數外部創建。我們不應該在工程和應用里硬編碼所有的widget,這樣它們就完全不能重新被創建。更建議是把創建在函數外部的抽離成可以傳參的組件。
center組件只對它第一層child有效
自定義Widget
自定義widget可以為應用添加個性化風格,可以設計符合品牌或主題的內容。widget完全可以自定義,我們也可以擴展和自定義現有widget,或將它們作為整體框架來使用。
route 是頁面的別名
Clarification - a route takes you to a page , or screen.
our code and animation mentions 'categoryRoute' and 'converterRoute' , but these are more aptly named 'categoryScreen' and 'converterScreen'. These widgets are responsible for the UI at the route's destination.
有狀態的組件 stateful widget
之前我們說widget是不可修改的,也就是說它沒有狀態可言,也不能在創建之后再修改。我們在應用中加入一些狀態,這樣widget就可能因為用戶操作而發生一些變化。
當用戶進行操作widget發生變化時,我們希望保持狀態并響應用戶交互和事件,這時候就需要用到stateful widget。
stateful widget本身也是不可改變的
因為它本身的不可改變,所以它用createState方法創建一個state對象。state對象存儲了widgetState信息并且可以在對象的生命周期中發生改變。實例化過程中傳入的文本是不可改變的,但是存在于狀態對象中的顏色卻是可以改變的。仔細觀察我們的stateful組件的方法,下劃線開頭的類說明它是一個私有類,因而唯一能夠訪問其資源的就是類本身了。在Flutter中為了保持這種一致性,所以我們會看到狀態對象也命名為對應的statefulWidget。
下面來聊一聊狀態對象本身。它提供了一套可調用的方法用于改變各種狀態,比如說,如果我們要在每次點擊矩形的時候改變其顏色,為了方便我們選取FlatButton組件作為容器,這樣就可以使用onPressed事件屬性了。這個widget在狀態的數據發生改變時不會自動觸發重新渲染的過程。改變顏色這個事情就交給了widget的實現者來調用setState方法,在下一幀中觸發重建。
僅僅只有發生改變的widget會被重建
要注意的是文本是不會被修改的,因為它沒有存儲在狀態對象中。當Stateful組件被初始化的時候,我們可以通過重新initState方法來加入一些自定義的代碼。
Stateful 組件也可以用于動畫,這一過程發生在UI從一個狀態變為另一個狀態的過程中?;龅牟藛尉褪且粋€簡單的例子,它內部存儲了關閉、隱藏和開啟三個狀態屬性,
顏色
顏色的透明,范圍從00開始,即完全透明,可增加到FF,即完全不透明。
Text Input
能夠吸引用戶的應用有什么秘訣?當然是人機交互了。人機交互包括文本輸入,下拉列表選項,手勢和數據API交互等等。Flutter的Widget通常以內置方式捕捉用戶的操作,我們先從文本的輸入開始聊。
在Flutter中通過TextField組件來捕獲文本輸入,如果要創建表格的話就需要用TextFormField小部件。
通過keyboardType 來設置虛擬鍵盤類型
字符
Flutter支持Unicode和emojis。
那么如何獲取用戶輸入的字符串呢?類似前面所說,在Flutter中都是通過用戶的操作來進行相應的反饋進而修改Widget小部件的內置屬性。檢索用戶輸入的文本有三種方法,onChanged方法、onSubmitted方法和controller控制器。
- onChanged屬性接受一個函數,每次文本更改時都會調用此函數。在函數內部可以設置狀態,檢查文本的輸入是否存在驗證錯誤。
- 當用戶點擊return鍵時,將調用onSubmitted屬性,同樣也傳遞一個函數來執行此操作。只有用戶完成輸入后才做響應。這種對于需要驗證數據有效性的場景十分有用。
- 控制器屬性允許對用戶輸入進行更多自定義和控制。我們可以創建并傳入文本編輯控制器,添加監聽此控制器的listener,這不僅允許我們在用戶輸入每個字符時進行響應,還允許我們修改和覆蓋文本字段的內容。比如自動補全功能。
在用戶操作文本輸入這一塊,我們可以定義鍵盤為數字鍵盤、郵件鍵盤,但是我們沒有辦法規避用戶輸入的空格,所以在每一個input框中都最好加入驗證。
手勢
Flutter的手勢系統中有兩個獨立的層,第一層時原始指針事件,它描述了指針在屏幕上的位置和移動,這些包括指針向下、指針移動、指針向上和指針取消事件。第二層是手勢,手勢表示從多個單獨指針事件中識別的語義動作,比如點擊、拖動和縮放。
手勢在生命周期中傳遞多重事件,如果我們想要將自己的交互性添加任意一個小部件上,我們可以將這個widget小部件包裹在GestureDetector小部件中。
響應式
響應式,意味著我們需要一種基于屏幕方向或其高度和寬度重新調整子widget的方法。在Flutter中,可以使用媒體查詢(media query)、方向構建器(orientation builder)和布局構建器(layout builder)。
MediaQuery.of 可以提供有關當前設備上運行的應用程序的大小,方向和其他數據。
orientation builder widget方向構建器的builder屬性,是一個將父類widget方向作為參數的函數,我們可以使用此方向來布局子widget。
布局構建器的builder屬性是一個帶有盒子約束參數的函數,盒子約束可以提供有關當前尺寸約束的信息,比如父類widget的高度或縱橫比。
這兩個構建器和媒體查詢之間的區別在于它們可以特定于一個小部件而不是這個應用程序。同樣如果我們使用的是構建器,那么在用戶更改APP的尺寸大小時,構建函數會自動重新運行。
還有一些其他的方法,比如在widget周圍包含寬高比(aspect ratio 小部件)來強制使用某個寬高比。比如使用Fitted box 小部件來根據子widget來進行縮放。
package plugin和pubspec.yaml
就像widget小部件一樣,我們在開發新的應用或功能時都不希望重復造輪子。
package可以創建易于共享的模塊化代碼,我們可以使用Flutter和Dart包,有些與設備API集成比如Battery包。有些被稱為插件,因為它們與iOS或Android平臺交互,比如Firebase包。
在pubspec.yaml中包含的信息有名稱、版本、描述、作者和依賴項。Flutter應用程序會依賴Flutter SDK。我們還可以在Pubspec中指定資源和字體。當我們對Pubspec進行更改時,如果編輯器沒有自動執行此操作,我們還可以通過命令 flutter packages get
來執行。這個命令會獲取或更新應用包所依賴的必須軟件包。Pub創建一個a.packages文件,該文件從包名稱映射到那些實際的URI中。即便是導入了大型的軟件包,也只有所調用的函數最終會在發布模式下編譯為代碼,這是因為Flutter使用了tree shaking來刪除生產環境中的二進制文件在編譯過程中產生的冗余和未使用的代碼。
tree shaking:process where redundant and unused code is removed during code compilation.
通過這樣,我們無需指定要導入的特定類,而只需要導入整個包且不用擔心有未使用的依賴項。
File assets
所謂 assets,指的是和APP捆綁和部署的文件。在運行時可以訪問并且是只讀的。我們可以通過AssetBundle來訪問這個文件夾。Flutter中還附帶了rootBundle來訪問主資源包。這里我建議使用DefaultAssetBundle來獲得當前的資源。在定位和運行測試的時候,可以使父類widget在運行時替換資源。
使用DefaultAssetBundle.of 來間接加載資源,然后導入dart convert 包來將每一個單元映射到對應的類里。
異步
API的調用可能需要一些時間,在flutter中我們將它包裹在異步操作中。這么操作可以讓應用程序繼續運行而不會被阻止。
當調用返回future的函數時,會發生兩件事。首先,該功能提示要完成的工作并返回一個不完整的future對象;然后,當值可用時,future對象將使用該值或返回一個錯誤。
當值可用時,將future返回的值保存到變量中,并在函數上調用await,我們需要使用async關鍵字包裝調用的函數。
關于錯誤,我們可以使用條件檢查和try-catch語句來捕捉錯誤,并且根據所捕捉的錯誤在頁面中顯示與錯誤消息關聯的UI小部件。