做者:hicchtml
| 導語 最全Flutter教程,涉及Dart語言,Flutter Widget,Flutter狀態管理等。前端
最愛折騰的就是前端工程師了,從 jQuery 折騰到 AngularJs,再折騰到 Vue、React。 最愛跨屏的也是前端工程師,從 phonegap,折騰到 React Native,這不又折騰到了 Flutter。react
圖啥?android
低成本地爲用戶帶來更優秀的用戶體驗。ios
目前來講Flutter多是其中最優秀的一種方案了。git
Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktopfrom a single codebase.github
Flutter是由原 Google Chrome 團隊成員,利用 Chrome 2D 渲染引擎,而後精簡 CSS 佈局演變而來。web
[ Flutter 架構 ]shell
或者更詳細的版本:npm
從架構中實際上已經能看出 Flutter 爲何快,至少相比以前的當紅炸子雞 React Native 快的緣由了。
而相比 React Native:
而具體二者的性能測試,能夠看這裏,結論是 Flutter,在 CPU,FPS,內存穩定上均優於 ReactNative。
在開始 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
=
和其餘符號的組合: *=
、 ~/=
、 &=
、 |=
……// 想一想這樣省了多少變量聲明
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 hashCode
getter
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 對象的時候尤爲有用。
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。
在這裏單獨提出來的意義在於,從 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
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 對此的優化看這裏。
到這裏終於到正題了,若是熟悉 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,包括但不限於
Widget 是 Dart 中特殊的類,經過實例化(Dart 中new 是可選的)相互嵌套,你的這個 App 就是形以下圖的一顆組件樹(Dart 入口函數的概念, main.dart->main()
)。
[ Flutter Widget Tree ]
上說過 Flutter 佈局思路來自 CSS,而 Flutter 中一切皆 Widget,所以總體佈局也很簡單:
容器組件 Container
Padding,Center
Row,Column,Flex
Wrap, Flow 流式佈局
stack, z 軸佈局
……
Flutter 中 Widget 能夠分爲三類,形如 React 中「展現組件」、「容器組件」,「context」。
這個就是 Flutter 中的「展現組件」,自身不保存狀態,外部參數變化就銷燬從新建立。Flutter 建議儘可能使用無狀態的組件。
狀態組件就是相似於 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,開發體驗能夠說挺不錯的。
setState()
能夠很方便的管理組件內的數據,可是 Flutter 中狀態一樣是從上往下流轉的,所以也會遇到和 React 中一樣的問題,若是組件樹太深,逐層狀態建立就顯得很麻煩了,更不要說代碼的易讀和易維護性了。
一樣 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是 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 的問題在於
順便,今年 Apple 也擁抱了響應式,Combine(Rx like) + SwiftUI 也基本等於 Bloc 了。
因此,Rx 仍是要趕忙學起來 :grimacing:
除去 Bloc,Flutter 中仍是可使用其餘的方案,譬如:
展開來講如今的前端開發使用強大的框架頁面組裝已經不是難點了。開發的難點在於如何組合富交互所需的數據,也就是上面圖中的 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 ]
這些還在開發當中,鑑於對 Dart 喜歡,以及對 Flutter 性能的樂觀,這些卻是很值得期待。
[ Flutter web 架構 ]
還記得平臺只是給 Flutter 提供一個畫布麼,Flutter Desktop 將來更是能夠大有可爲 :smile:,相關能夠看這裏。
最後每種方案,每種技術都有優缺點,甚至技術的架構決定了,有些缺陷可能永遠都無法改進,因此 [emoji]。
關注【IVWEB社區】公衆號獲取每週最新文章,通往人生之巔!