您好,登錄后才能下訂單哦!
| 導語 最全Flutter教程,涉及Dart語言,Flutter Widget,Flutter狀態管理等。
最愛折騰的就是前端工程師了,從 jQuery 折騰到 AngularJs,再折騰到 Vue、React。 最愛跨屏的也是前端工程師,從 phonegap,折騰到 React Native,這不又折騰到了 Flutter。
圖啥?
低成本地為用戶帶來更優秀的用戶體驗。
目前來說Flutter可能是其中最優秀的一種方案了。
Flutter 是什么?
Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktopfrom a single codebase.
Flutter是由原 Google Chrome 團隊成員,利用 Chrome 2D 渲染引擎,然后精簡 CSS 布局演變而來。
[ Flutter 架構 ]
或者更詳細的版本:
Flutter 在各個原生的平臺中,使用自己的 C++的引擎渲染界面,沒有使用 webview,也不像 RN、NativeScript 一樣使用系統的組件。簡單來說平臺只是給 Flutter 提供一個畫布。
界面使用 Dart 語言開發,貌似唯一支持 JIT,和 AOT 模式的強類型語言。
寫法非常的現代,聲明式,組件化,Composition > inheritance,響應式……就是現在前端流行的這一套 :smile:
一套代碼搞定所有平臺。
Flutter 為什么快?Flutter 相比 RN 的優勢在哪里?
從架構中實際上已經能看出 Flutter 為什么快,至少相比之前的當紅炸子雞 React Native 快的原因了。
Skia 引擎,Chrome, Chrome OS,Android,Firefox,Firefox OS 都以此作為渲染引擎。
Dart 語言可以 AOT 編譯成 ARM Code,讓布局以及業務代碼運行的最快,而且 Dart 的 GC 針對 Flutter 頻繁銷毀創建 Widget 做了專門的優化。
CSS 的子集 Flex like 的布局方式,保留強大表現能力的同時,也保留了性能。
Flutter 業務書寫的 Widget 在渲染之前 diff 轉化成 Render Object,對,就像 React 中的 Virtual DOM,以此來確保開發體驗和性能。
而相比 React Native:
RN 使用 JavaScript 來運行業務代碼,然后 JS Bridge 的方式調用平臺相關組件,性能比有損失,甚至平臺不同 js 引擎都不一樣。
RN 使用平臺組件,行為一致性會有打折,或者說,開發者需要處理更多平臺相關的問題。
而具體兩者的性能測試,可以看這里,結論是 Flutter,在 CPU,FPS,內存穩定上均優于 ReactNative。
Dart 語言
在開始 Flutter 之前,我們需要先了解下 Dart 語言……
Dart 是由 Google 開發,最初是想作為 JavaScript 替代語言,但是失敗沉寂之后,作為 Flutter 獨有開發語言又煥發了第二春 :joy:。
實際上即使到了 2.0,Dart 語法和 JavaScriptFlutter非常的相像。單線程,Event Loop……
[ Dart Event Loop模型 ]
當然作為一篇寫給前端工程師的教程,我在這里只想寫寫 JavaScript 中暫時沒有的,Dart 中更為省心,也更“甜”的東西。
不會飄的 this
強類型,當然前端現在有了 TypeScript :grimacing:
強大方便的操作符號:
?. 方便安全的 foo?.bar取值,如果 foo 為 null,那么取值為 null
?? condition?expr1:expr2 可以簡寫為 expr1??expr2
=和其他符號的組合: *=、 ~/=、 &=、 |= ……
級聯操作符(Cascade notation ..)
//?想想這樣省了多少變量聲明 querySelect('#button') ?..text?="Confirm" ?..classes.add('important') ?..onClick.listen((e)?=>?window.alert('Confirmed')) 復制代碼
甚至可以重寫操作符
class?Vector?{ ?final?int?x,?y; ?Vector(this.x,?this.y); ?Vector?operator?+(Vector?v)?=>?Vector(x?+?v.x,?y?+?v.y); ?Vector?operator?-(Vector?v)?=>?Vector(x?-?v.x,?y?-?v.y); ?//?Operator?==?and?hashCode?not?shown.?For?details,?see?note?below. ?//?··· } void?main()?{ ?final?v?=?Vector(2,?3); ?final?w?=?Vector(2,?2); ?assert(v?+?w?==?Vector(4,?5)); ?assert(v?-?w?==?Vector(0,?1)); } 復制代碼
注:重寫 ==,也需要重寫 Object hashCodegetter
class?Person?{ ?final?String?firstName,?lastName; ?Person(this.firstName,?this.lastName); ?//?Override?hashCode?using?strategy?from?Effective?Java, ?//?Chapter?11. ?@override ?int?get?hashCode?{ ?int?result?=?17; ?result?=?37?*?result?+?firstName.hashCode; ?result?=?37?*?result?+?lastName.hashCode; ?return?result; ?} ?//?You?should?generally?implement?operator?==?if?you ?//?override?hashCode. ?@override ?bool?operator?==(dynamic?other)?{ ?if?(other?is!?Person)?return?false; ?Person?person?=?other; ?return?(person.firstName?==?firstName?&& ?person.lastName?==?lastName); ?} } void?main()?{ ?var?p1?=?Person('Bob',?'Smith'); ?var?p2?=?Person('Bob',?'Smith'); ?var?p3?=?'not?a?person'; ?assert(p1.hashCode?==?p2.hashCode); ?assert(p1?==?p2); ?assert(p1?!=?p3); } 復制代碼
這點在 diff 對象的時候尤其有用。
lsolate
Dart 運行在獨立隔離的 iSolate 中就類似 JavaScript 一樣,單線程事件驅動,但是 Dart 也開放了創建其他 isolate,充分利用 CPU 的多和能力。
loadData()?async?{ ?//?通過spawn新建一個isolate,并綁定靜態方法 ?ReceivePort?receivePort?=ReceivePort(); ?await?Isolate.spawn(dataLoader,?receivePort.sendPort); ?//?獲取新isolate的監聽port ?SendPort?sendPort?=?await?receivePort.first; ?//?調用sendReceive自定義方法 ?List?dataList?=?await?sendReceive(sendPort,?'https://hicc.me/posts'); ?print('dataList?$dataList'); } //?isolate的綁定方法 static?dataLoader(SendPort?sendPort)?async{ ?//?創建監聽port,并將sendPort傳給外界用來調用 ?ReceivePort?receivePort?=ReceivePort(); ?sendPort.send(receivePort.sendPort); ?//?監聽外界調用 ?await?for?(var?msg?in?receivePort)?{ ?String?requestURL?=msg[0]; ?SendPort?callbackPort?=msg[1]; ?Client?client?=?Client(); ?Response?response?=?await?client.get(requestURL); ?List?dataList?=?json.decode(response.body); ?//?回調返回值給調用者 ?callbackPort.send(dataList); ?}? } //?創建自己的監聽port,并且向新isolate發送消息 Future?sendReceive(SendPort?sendPort,?String?url)?{ ?ReceivePort?receivePort?=ReceivePort(); ?sendPort.send([url,?receivePort.sendPort]); ?//?接收到返回值,返回給調用者 ?return?receivePort.first; } 復制代碼
當然 Flutter 中封裝了compute,可以方便的使用,譬如在其它 isolate 中解析大的 json。
Dart UI as Code
在這里單獨提出來的意義在于,從 React 開始,到 Flutter,到最近的 Apple SwiftUI,Android Jetpack Compose 聲明式組件寫法越發流行,Web 前端使用 JSX 來讓開發者更方便的書寫,而 Flutter,SwiftUI 則直接從優化語言本身著手。
函數類的命名參數
void?test({@required?int?age,String?name})?{ ?print(name); ?print(age); } //?解決函數調用時候,參數不明確的問題 test(name:"hicc",age:?30) //?這樣對于組件的使用尤為方便 class?MyApp?extends?StatelessWidget?{ ?@override ?Widget?build(BuildContext?context)?{ ?return?Scaffold( ?appBar:?AppBar(), ?body:?Container(), ?floatingActionButton:FloatingActionButton() ?); ?} } 復制代碼
大殺器:Collection If 和 Collection For
//?collection?If Widget?build(BuildContext?context)?{ ?return?Row( ?children:?[ ?IconButton(icon:?Icon(Icons.menu)), ?Expanded(child:?title), ?if?(!isAndroid) ?IconButton(icon:?Icon(Icons.search)), ?], ?); } 復制代碼 //?Collect?For var?command?=?[ ?engineDartPath, ?frontendServer, ?for?(var?root?in?fileSystemRoots)?"--filesystem-root=$root", ?for?(var?entryPoint?in?entryPoints) ?if?(fileExists("lib/$entryPoint.json"))?"lib/$entryPoint", ?mainPath ]; 復制代碼
更多 Dart 2.3 對此的優化看這里。
Flutter 怎么寫
到這里終于到正題了,如果熟悉 web 前端,熟悉 React 的話,你會對下面要講的異常的熟悉。
[ UI=F(state) ]
Flutter App 的一切從 lib/main.dart文件的 main 函數開始:
import?'package:flutter/material.dart'; void?main()?=>?runApp(MyApp()); class?MyApp?extends?StatelessWidget?{ ?@override ?Widget?build(BuildContext?context)?{ ?return?MaterialApp( ?title:?'Welcome?to?Flutter', ?home:?Scaffold( ?appBar:?AppBar( ?title:?Text('Welcome?to?Flutter'), ?), ?body:?Center( ?child:?Text('Hello?World'), ?), ?), ?); ?} } 復制代碼
Dart 類 build 方法返回的便是 Widget,在 Flutter 中一切都是 Widget,包括但不限于
結構性元素,menu,button 等
樣式類元素,font,color 等
布局類元素,padding,margin 等
導航
手勢
Widget 是 Dart 中特殊的類,通過實例化(Dart 中new 是可選的)相互嵌套,你的這個 App 就是形如下圖的一顆組件樹(Dart 入口函數的概念, main.dart->main())。
[ Flutter Widget Tree ]
Widget 布局
上說過 Flutter 布局思路來自 CSS,而 Flutter 中一切皆 Widget,因此整體布局也很簡單:
容器組件 Container
decoration 裝飾屬性,設置背景色,背景圖,邊框,圓角,陰影和漸變等
margin
padding
alignment
width
height
Padding,Center
Row,Column,Flex
Wrap, Flow 流式布局
stack, z 軸布局
……
更多可以看這里
Flutter 中 Widget 可以分為三類,形如 React 中“展示組件”、“容器組件”,“context”。
StatelessWidget
這個就是 Flutter 中的“展示組件”,自身不保存狀態,外部參數變化就銷毀重新創建。Flutter 建議盡量使用無狀態的組件。
StatefulWidget
狀態組件就是類似于 React 中的“容器組件”了,Flutter 中狀態組件寫法會稍微不一樣。
class?Counter?extends?StatefulWidget?{ ?//?This?class?is?the?configuration?for?the?state.?It?holds?the ?//?values?(in?this?case?nothing)?provided?by?the?parent?and?used?by?the?build ?//?method?of?the?State.?Fields?in?a?Widget?subclass?are?always?marked?"final". ?@override ?_CounterState?createState()?=>?_CounterState(); } class?_CounterState?extends?State<Counter>?{ ?int?_counter?=?0; ?void?_increment()?{ ?setState(()?{ ?//?This?call?to?setState?tells?the?Flutter?framework?that ?//?something?has?changed?in?this?State,?which?causes?it?to?rerun ?//?the?build?method?below?so?that?the?display?can?reflect?the ?//?updated?values.?If?you?change?_counter?without?calling ?//?setState(),?then?the?build?method?won't?be?called?again, ?//?and?so?nothing?would?appear?to?happen. ?_counter++; ?}); ?} ?@override ?Widget?build(BuildContext?context)?{ ?//?This?method?is?rerun?every?time?setState?is?called,?for?instance ?//?as?done?by?the?_increment?method?above. ?//?The?Flutter?framework?has?been?optimized?to?make?rerunning ?//?build?methods?fast,?so?that?you?can?just?rebuild?anything?that ?//?needs?updating?rather?than?having?to?individually?change ?//?instances?of?widgets. ?return?Row( ?children:?<Widget>[ ?RaisedButton( ?onPressed:?_increment, ?child:?Text('Increment'), ?), ?Text('Count:?$_counter'), ?], ?); ?} } 復制代碼
可以看到 Flutter 中直接使用了和 React 中同名的 setState方法,不過不會有變量合并的東西,當然也有生命周期。
[ Flutter StatefulWidget 聲明周期 ]
可以看到一個有狀態的組件需要兩個 Class,這樣寫的原因在于,Flutter 中 Widget 都是 immmutable 的,狀態組件的狀態保存在 State 中,組件仍然每次重新創建,Widget 在這里只是一種對組件的描述,Flutter 會 diff 轉換成 Element,然后轉換成 RenderObject 才渲染。
[ Flutter render object ]
Flutter Widget 更多的渲染流程可以看這里。
實際上 Widget 只是作為組件結構一種描述,還可以帶來的好處是,你可以更方便的做一些主題性的組件, Flutter 官方提供的Material Components widgets和Cupertino (iOS-style) widgets質量就相當高,再配合 Flutter 亞秒級的Hot Reload,開發體驗可以說挺不錯的。
State Management
setState()可以很方便的管理組件內的數據,但是 Flutter 中狀態同樣是從上往下流轉的,因此也會遇到和 React 中同樣的問題,如果組件樹太深,逐層狀態創建就顯得很麻煩了,更不要說代碼的易讀和易維護性了。
InheritedWidget
同樣 Flutter 也有個 context一樣的東西,那就是 InheritedWidget,使用起來也很簡單。
class?GlobalData?extends?InheritedWidget?{ ?final?int?count; ?GlobalData({Key?key,?this.count,Widget?child}):super(key:key,child:child); ?@override ?bool?updateShouldNotify(GlobalData?oldWidget)?{ ?return?oldWidget.count?!=?count; ?} ?static?GlobalData?of(BuildContext?context)?=>?context.inheritFromWidgetOfExactType(GlobalData); } class?MyApp?extends?StatelessWidget?{ ?//?This?widget?is?the?root?of?your?application. ?@override ?Widget?build(BuildContext?context)?{ ?return?MaterialApp( ?title:?'Flutter?Demo', ?theme:?ThemeData( ?primarySwatch:?Colors.blue, ?), ?home:?MyHomePage(title:?'Flutter?Demo?Home?Page'), ?); ?} } class?MyHomePage?extends?StatefulWidget?{ ?MyHomePage({Key?key,?this.title})?:?super(key:?key); ?final?String?title; ?@override ?_MyHomePageState?createState()?=>?_MyHomePageState(); } class?_MyHomePageState?extends?State<MyHomePage>?{ ?int?_counter?=?0; ?void?_incrementCounter()?{ ?_counter++; ?}); ?} ?@override ?Widget?build(BuildContext?context)?{ ?return?Scaffold( ?appBar:?AppBar( ?title:?Text(widget.title), ?), ?body:?GlobalData( ?count:?_counter, ?child:?Center( ?child:?Column( ?mainAxisAlignment:?MainAxisAlignment.center, ?children:?<Widget>[ ?Text( ?'You?have?pushed?the?button?this?many?times:', ?), ?Text( ?'$_counter', ?style:?Theme.of(context).textTheme.display1, ?), ?Body(), ?Body2() ?], ?), ?), ?), ?floatingActionButton:?FloatingActionButton( ?onPressed:?_incrementCounter, ?tooltip:?'Increment', ?child:?Icon(Icons.add), ?), ?); ?} } class?Body?extends?StatelessWidget?{ ?@override ?Widget?build(BuildContext?context)?{ ?GlobalData?globalData?=?GlobalData.of(context); ?return?Text(globalData.count.toString()); ?} } class?Body2?extends?StatelessWidget?{ ?@override ?Widget?build(BuildContext?context)?{ ?//?TODO:?implement?build ?GlobalData?globalData?=?GlobalData.of(context); ?return?Text(globalData.count.toString()); ?} 復制代碼
具體實現原理可以參考這里,不過 Google 封裝了一個更為上層的庫provider,具體使用可以看這里。
BlOC
BlOC是 Flutter team 提出建議的另一種更高級的數據組織方式,也是我最中意的方式。簡單來說:
Bloc = InheritedWidget + RxDart(Stream)
Dart 語言中內置了 Steam,Stream ~= Observable,配合RxDart, 然后加上 StreamBuilder會是一種異常強大和自由的模式。
class?GlobalData?extends?InheritedWidget?{ ?final?int?count; ?final?Stream<String>?timeInterval$?=?new?Stream.periodic(Duration(seconds:?10)).map((time)?=>?new?DateTime.now().toString()); ?GlobalData({Key?key,?this.count,Widget?child}):super(key:key,child:child); ?@override ?bool?updateShouldNotify(GlobalData?oldWidget)?{ ?return?oldWidget.count?!=?count; ?} ?static?GlobalData?of(BuildContext?context)?=>?context.inheritFromWidgetOfExactType(GlobalData); } class?TimerView?extends?StatelessWidget?{ ?@override ?Widget?build(BuildContext?context)?{ ?GlobalData?globalData?=?GlobalData.of(context); ?return?StreamBuilder( ?stream:?globalData.timeInterval$, ?builder:?(context,?snapshot)?{ ?return?Text(snapshot?.data????''); ?} ?); ?} } 復制代碼
當然 Bloc 的問題在于
學習成本略高,Rx 的概念要吃透,不然你會抓狂
自由帶來的問題是,可能代碼不如 Redux 類的規整。
順便,今年 Apple 也擁抱了響應式,Combine(Rx like) + SwiftUI 也基本等于 Bloc 了。
所以,Rx 還是要趕緊學起來 :grimacing:
除去 Bloc,Flutter 中還是可以使用其他的方案,譬如:
Flutter Redux
阿里閑魚的Fish Redux,據說性能很好。
Mobx
……
展開來說現在的前端開發使用強大的框架頁面組裝已經不是難點了。開發的難點在于如何組合富交互所需的數據,也就是上面圖中的 state部分。
更具體來說,是怎么優雅,高效,易維護地處理短暫數據(ephemeral state) setState()和需要共享的 App State 的問題,這是個工程性的問題,但往往也是日常開發最難的事情了,引用 Redux 作者 Dan 的一句:
“The rule of thumb is:Do whatever is less awkward.”
到這里,主要的部分已經講完了,有這些已經可以開發出一個不錯的 App 了。剩下的就當成一個 bonus 吧。
測試
Flutter debugger,測試都是出場自帶,用起來也不難。
//?測試在/test/目錄下面 void?main()?{ ?testWidgets('Counter?increments?smoke?test',?(WidgetTester?tester)?async?{ ?//?Build?our?app?and?trigger?a?frame. ?await?tester.pumpWidget(MyApp()); ?//?Verify?that?our?counter?starts?at?0. ?expect(find.text('0'),?findsOneWidget); ?expect(find.text('1'),?findsNothing); ?//?Tap?the?'+'?icon?and?trigger?a?frame. ?await?tester.tap(find.byIcon(Icons.add)); ?await?tester.pump(); ?//?Verify?that?our?counter?has?incremented. ?expect(find.text('0'),?findsNothing); ?expect(find.text('1'),?findsOneWidget); ?}); } 復制代碼
包管理,資源管理
類似與 JavaScript 的 npm,Flutter,也就是 Dart 也有自己的包倉庫。不過項目包的依賴使用 yaml 文件來描述:
name:?app description:?A?new?Flutter?project. version:?1.0.0+1 environment: ?sdk:?">=2.1.0?<3.0.0" dependencies: ?flutter: ?sdk:?flutter ?cupertino_icons:?^0.1.2 復制代碼
生命周期
移動應用總歸需要應用級別的生命周期,flutter 中使用生命周期鉤子,也非常的簡單:
class?MyApp?extends?StatefulWidget?{ ?@override ?_MyAppState?createState()?=>?new?_MyAppState(); } class?_MyAppState?extends?State<MyApp>?with?WidgetsBindingObserver?{ ?@override ?void?initState()?{ ?super.initState(); ?WidgetsBinding.instance.addObserver(this); ?} ?@override ?void?dispose()?{ ?WidgetsBinding.instance.removeObserver(this); ?super.dispose(); ?} ?@override ?void?didChangeAppLifecycleState(AppLifecycleState?state)?{ ?switch?(state)?{ ?case?AppLifecycleState.inactive: ?print('AppLifecycleState.inactive'); ?break; ?case?AppLifecycleState.paused: ?print('AppLifecycleState.paused'); ?break; ?case?AppLifecycleState.resumed: ?print('AppLifecycleState.resumed'); ?break; ?case?AppLifecycleState.suspending: ?print('AppLifecycleState.suspending'); ?break; ?} ?super.didChangeAppLifecycleState(state); ?} ?@override ?Widget?build(BuildContext?context)?{ ?return?Container(); ?} } 復制代碼
使用原生能力
和 ReactNative 類似,Flutter 也是使用類似事件的機制來使用平臺相關能力。
[ Flutter platform channels ]
Flutter Web, Flutter Desktop
這些還在開發當中,鑒于對 Dart 喜歡,以及對 Flutter 性能的樂觀,這些倒是很值得期待。
[ Flutter web 架構 ]
還記得平臺只是給 Flutter 提供一個畫布么,Flutter Desktop 未來更是可以大有可為 :smile:,相關可以看這里。
最后每種方案,每種技術都有優缺點,甚至技術的架構決定了,有些缺陷可能永遠都沒法改進,所以 [emoji]。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。