前言
要說2018年最火的跨端技術,當屬於 Flutter
莫屬,應該沒人質疑吧。一個新的技術的趨勢,最明顯的特徵,就是它一定想把“前浪”拍死在沙灘上。這個前浪,就是"react Native","weex"。目前隨便在搜尋引擎上 搜尋"Flutter reactNative",就全是這兩個技術的對比,評測。
一股股濃濃 : 不服來 “掰” 啊 !!!的味道。
是的,錯過了react Native
, weex
這些 “炸” 翻前端的技術,不能在錯過 Flutter
了,這年頭,你不會一門,跨端技術,怎麼好意思說自己是【前端】。
Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生使用者介面。 Flutter可以與現有的程式碼一起工作。在全世界... ...
好了, 這些,大家早就知道了,來點實在的!!!
話說隔壁師兄,“閒魚” 是最早一批與谷歌展開合作,並在重要的商品詳情頁中使用flutter技術上線的BU。一路走來,積累了大量的開發經驗。“閒魚” flutter 相關文章的都很有深度,足見功力。
都是一篇篇的深度好文,讀完收益匪淺,但是對於剛接觸 Flutter
的web前端同學來說還是
好了,回到標題,筆者作為一名傳統 web前端,想從前端最熟悉的視角 “躺” 著把 Flutter
瞭解一遍,不要敬仰,平視它!!!先從興趣開始。
正文
Flutter 環境的搭建,其實有很多資源可以參考。這裡就不累述了(知道有很多坑,在後續文章中,有機會把個人遇到的坑彙總一下 )。
有興趣可以參考 flutter安裝環境的搭建 , 在這裡建議各位,一定要自己親自搭一下環境,跑一下官方demo, 小馬過河,焉知深淺,自己定的位才是最準確的。
有了環境,先配置編輯器。
然後 建立一個Flutter專案。
專案建立完成,可以先用 flutter run 跑一下。
flutter run
複製程式碼
好了,跑起來了吧,你會看到一個計數的官方示例,點選加號圖片可以做加運算。
這時候我們看專案的project 目錄裡 有一個入口檔案叫 main.dart。然後開啟 main.dart 就像下面這樣:
( 為什麼你們看到程式碼比我的長,因為我摺疊了!!! ) 這不是重點,重點是每個類繼承的都是一個尾號為Widget
的字元。
聰明的你一定會覺得 Widget
和 Flutter
有著某種神祕的聯絡。
“Binggo!",是的,Flutter
有兩個重型武器,一個叫 Dart
,另一個就是 Widget
了。
Dart
一切皆來自 Object
, Flutter
的元件皆來自 Widget
。
關於強大的Dart,今天暫且不表,後面有時間可以獨立篇幅來聊聊Dart。
先祭出一張 Flutter
的架構老圖。
在 Flutter
的世界裡,包括views,view controllers,layouts等在內的概念都建立在Widget之上。
Widget
是 Flutter
元件的抽象描述。所以掌握Flutter的基礎就是學會使用 Widget
開始。
在Flutter介面渲染過程分為三個階段:佈局、繪製、合成,佈局和繪製在Flutter框架中完成,合成則交由引擎負責:
Flutter 通過組合、巢狀不同型別的控制元件,就可以構建出任意功能、任意複雜度的介面。
- 它包含的最主要的幾個類有:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding,PaintingBinding, RendererBinding, WidgetsBinding { ... }
abstract class Widget extends DiagnosticableTree { ... }
abstract class StatelessWidget extends Widget { ... }
abstract class StatefulWidget extends Widget { ... }
abstract class RenderObjectWidget extends Widget { ... }
abstract class Element extends DiagnosticableTree implements BuildContext { ... }
abstract class RenderObjectElement extends Element { ... }
class StatelessElement extends ComponentElement { ... }
class StatefulElement extends ComponentElement { ... }
複製程式碼
上面這些類的主要作用如下:
- 基於Flutter控制元件系統開發的程式都需要使用WidgetsFlutterBinding,它是Flutter的控制元件框架和Flutter引擎的膠水層。
- Widget就是所有控制元件的基類,它本身所有的屬性都是隻讀的。
- RenderObjectWidget所有的實現類則負責提供配置資訊並建立具體的RenderObjectElement。
- Element是Flutter用來分離控制元件樹和真正的渲染物件的中間層,控制元件用來描述對應的element屬性,控制元件重建後可能會複用同一個element。
- RenderObjectElement持有真正負責佈局、繪製和碰撞測試(hit test)的RenderObject物件。
- StatelessWidget和StatefulWidget並不會直接影響RenderObject建立,只負責建立對應的RenderObjectWidget
- StatelessElement和StatefulElement也是類似的功能。
很複雜是吧,先不用管,簡單表述Widget是這樣的:
Widget = 樣式(css) + 標記語義(標籤) + 元件化(官方) + 資料繫結(props)
“什麼?這不就是 react 嗎?"
對, React 的概念和
Flutter
的Widget
是有相通性的。
“既然有react的概念,難道還有state,setState嗎?“
又對, Flutter 還真有 類似 state 狀態機制的概念,而且也確實有 setState 的方法。
“dome裡有兩個基類,StatelessWidget 和 StatefulWidget 是做什麼用的?”
StatelessWidget 和 StatefulWidget,這裡兩個類特別重要,幾乎所有的元件都是基於他們建立的。
StatelessWidget 是狀態不可變的widget,稱為
無狀態widget
。初始狀態設定以後就不可再變化。如果需要變化需要重新建立。
StatefulWidget 可以儲存自己的狀態,稱為
有狀態widget
。Flutter
首先儲存了初始化時建立的State,狀態是通過改變State,來重新構建Widget
樹來進行UI變化。改變狀態的方法,就是我們用的最多的神器"setState",而單純改變資料是不會引發UI改變的,這個概念和我們的 React 一樣一樣的。
如果你是初次接觸 Flutter
可以不用記憶這麼多元件基類,只用記住以下式子就可以, 不誇張的說,熟悉這個式子就可以開發 Flutter 專案了:
拆解
圍繞著widget的構成,我們來拆解分析一下,標記語義,樣式,元件化。
標記語義
為什麼不稱為 “模版”,“標籤”,“element" ,而叫"標記語義",是因為flutter的 widget 結構並不只是 “模版”,“標籤”,“element"。widget 描述結構更像是 React 的虛擬dom階段,濃縮了相關上下文,以物件化的結構展示。
我們先來看看,React 建立出來的虛擬dom結構( 虛擬碼 ):
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
複製程式碼
再來看看,flutter 用Dart 建立的 widget 程式碼結構:
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Container(
padding: new EdgeInsets.only(top:100.0),
child: new Text('這是一個元件')
),
new Container(
decoration: new BoxDecoration(border: new Border.all(width:1.0,color: Colors.blue)),
padding: new EdgeInsets.all(20.0),
child: new Text('來自輸入框:'+active)
)
],
);
}
複製程式碼
是不是這樣看就熟悉很多了。
注:很多前端er 會不習慣這中書寫方式,目前有開發者在社群推動,在編譯前,使用jsx標籤,編譯後再解析成標記樹,比如這個DSX設計的提案。
雖然jsx->標記樹 還只是提案,但其實可以幫助我們更容易理解,此提案想表達的樣式像這樣:
class MyScaffold extends StatelessWidget {
build(context) {
return <Material>
<Column>
<MyAppBar
title={<Text
text='Example title'
style={Theme.of(context).primaryTextTheme.title},
/>}
/>
<Expanded>
<Center>
<Text text='Hello, world!'/>
</Center>
</Expanded>
</Column>
</Material>;
}
}
複製程式碼
上面這段 jsx 要是用 Dart 來寫是什麼樣的?如下:
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
MyAppBar(
title: Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
), // Text
), // MyAppBar
Expanded(
child: Center(
child: Text('Hello, world!'),
), // Center
), // Expanded
], // <Widget>[]
), // Column
); // Material
}
}
複製程式碼
雖然 Flutter
與 react 這麼類比多少有些牽強,但可以個人總結一些方法,方便理解:
- 開頭大寫的類名 相當於 jsx 的標籤名
- child 相當於 jsx 的 子標籤+佈局
- children 相當於 jsx 的 群組子標籤(和child還是有區別的)
- 其他屬性相當於 jsx 的 props
- 大家有沒有注意, 第二段dart 語法結尾都會帶上 “ // ” 註釋符號,這個是編輯器IDE在識別是
Flutter
專案後,自動追加上去的,像 jsx 語言的標籤封閉,方便發現標註的起始節點。
樣式
對於 Flutter
樣式的理解,可以檢視官方的這篇文件,也是同樣用類比的方式,很直觀的瞭解,HTML、css樣式和 flutter
之間的聯絡.
可以參考這裡:
flutter.io/docs/get-st…
flutterchina.club/web-analogs…
筆者摘選其中樣例的重點部分,對比展示來說明( 由於篇幅問題, 父子關係css 結構,用tab方式來表示 ):
左邊是css 寫法,右邊是 flutter-dart寫法
文字樣式:
.demo1 { background-color: #e0e0e0; width: 320px; height: 240px; font: 900 24px Georgia; letter-spacing: 4px; text-transform: uppercase; }
複製程式碼
複製程式碼
var demo1 = new Container(
child: new Text(
"Lorem ipsum".toUpperCase(), // 對應 左邊的文字轉換大小寫
style: new TextStyle( // 對應 左邊的 font
fontSize: 24.0
fontWeight: FontWeight.w900,
fontFamily: "Georgia",
letterSpacing: 4.0,
),
),
width: 320.0, // 對應 左邊的 width
height: 240.0, // 對應 左邊的 height
color: Colors.grey[300], // 對應 左邊的 background-color
);
複製程式碼
樣式居中:
.demo2 { display: flex; align-items: center; justify-content: center; }
複製程式碼
複製程式碼
var demo2 = new Container(
child: new Center( // 對應左邊的整個 flex 屬性
child: new Text("Lorem ipsum")
)
);
);
複製程式碼
設定最大(小)寬度:
.container{ width:300px .demo3 { width: 100%; max-width: 240px; } }
複製程式碼
複製程式碼
// 對於巢狀容器,如果父級的寬度小於子級寬度,則子級容器將自行調整大小以匹配父級。
var container = new Container(
child: new Center(
child: new Text("Lorem ipsum"),
decoration: new BoxDecoration( ... ), // constraints屬性,建立一個新的BoxConstraints來設定minWidth或maxWidth
width: 240.0, // 對應左邊的 max-width
),
width:300.0
),
複製程式碼
旋轉元件:
.box { transform: rotate(15deg); }
複製程式碼
複製程式碼
var container = new Container( // gray box
child: new Transform( // 對應左邊的 transform
child: new Container( ... ),
alignment: Alignment.center, // 對應左邊的 transform
transform: new Matrix4.identity() // 對應左邊的 transform
..rotateZ(15 * 3.1415927 / 180),
),
)
);
複製程式碼
縮放元件:
.box { transform: scale(1.5); } 複製程式碼
複製程式碼
var container = new Container( // gray box child: new Transform( // 對應左邊的 transform child: new Container( ... ), alignment: Alignment.center, // 對應左邊的 transform的中心 transform: new Matrix4.identity() // 對應左邊的 transform ..scale(1.5), ), ) ); 複製程式碼
複製程式碼
設定絕對位置和相對位置:
.greybox {
position: relative;
.redbox {
position: absolute;
top: 24px;
left: 24px;
}
}
複製程式碼
var container = new Container( // grey box child: new Stack( // 相對跟容器位置 relative children: [ new Positioned( // 相對父容器位置 absolute child: new Container( ... ), left: 24.0, top: 24.0, )], ) ); 複製程式碼
複製程式碼
顏色漸變:
.redbox { background: linear-gradient(180deg, #ef5350, rgba(0, 0, 0, 0) 80%); }
複製程式碼
複製程式碼
var container = new Container( // grey box child: new Center( child: new Container( // red box child: new Text( ... ), decoration: new BoxDecoration( gradient: new LinearGradient( // 對應左邊的 background: linear-gradient begin: const Alignment(0.0, -1.0), end: const Alignment(0.0, 0.6), colors: [ const Color(0xffef5350), const Color(0x00ef5350) ], ), ) ), ) ); 複製程式碼
複製程式碼
圓角:
.box { border-radius: 8px; } 複製程式碼
複製程式碼
var container = new Container( // grey box child: new Center( child: new Container( // red circle child: new Text( ... ), decoration: new BoxDecoration( borderRadius: new BorderRadius.all( const Radius.circular(8.0), ), ) ), ) ); 複製程式碼
複製程式碼
陰影:
.box { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8), 0 6px 20px rgba(0, 0, 0, 0.5); } 複製程式碼
複製程式碼
var container = new Container( // grey box child: new Center( child: new Container( // red box child: new Text( ... ), decoration: new BoxDecoration( boxShadow: [ // 對應左邊的 box-shadow new BoxShadow ( color: const Color(0xcc000000), offset: new Offset(0.0, 2.0), blurRadius: 4.0, ), new BoxShadow ( color: const Color(0x80000000), offset: new Offset(0.0, 6.0), blurRadius: 20.0, ), ], ) ), ) ); 複製程式碼
複製程式碼
畫圓:
.circle { text-align: center; width: 160px; height: 160px; border-radius: 50%; } 複製程式碼
複製程式碼
var container = new Container( // grey box child: new Center( child: new Container( // red circle child: new Text( ... ), decoration: new BoxDecoration( color: Colors.red[400], shape: BoxShape.circle, // 畫圓和圓角不太一樣,用的是BoxShape繪製影象能力 ), width: 160.0, height: 160.0, ), ) ); 複製程式碼
複製程式碼
內聯樣式:
// css 的內聯結構 .greybox { font: 900 24px Roboto; .redbox { em { font: 300 48px Roboto; font-style: italic; } } } 複製程式碼
複製程式碼
var container = new Container( // grey box child: new Center( child: new Container( // red box child: new RichText( text: new TextSpan( style: bold24Roboto, children: [ new TextSpan(text: "Lorem "), // 繼承內聯樣式 new TextSpan( text: "ipsum", style: new TextStyle( // 具有自定義樣式的單獨樣式 fontWeight: FontWeight.w300, fontStyle: FontStyle.italic, fontSize: 48.0, ), ), ], ), ), ), ) ); 複製程式碼
複製程式碼
元件化
官方的widgets目錄
點選每一個card後,裡面還有子card, 以一個展開的緯度來看,是這樣的:
這真是一個龐大的元件系統,這不是社群提供的,而是官方的。 flutter 團隊事無鉅細的實現了目前市面上基本上能見到的元件方式和型別。 個人認為這樣做優缺點並存的。 先說缺點:
-
1.學習成本增加,曲線也還是比較陡峭的。
-
2.元件直接的繼承關係路徑比較零亂,比如 繼承自 Widget 是這些大類道還清晰: PreferredSizeWidget ProxyWidget RenderObjectWidget StatefulWidget StatelessWidget。 但是,StatelessWidget和StatefulWidget的子類就過於平行化了,名稱上晦澀,沒有抽象架構化,分層或者塔型級別。
StatelessWidget:
StatefulWidget
- 3.對自定義元件定義模糊,有這麼龐大的元件庫,到底以後是有個一個更系統的第三方元件庫去替換它,還是說
Flutter
官方就不建議使用第三方元件,這個也未有定論。
再說優點:
-
1.可以阻止以後輪子氾濫,在團隊僵持不下使用哪個輪子庫時,最好的理由就是“官方”二字,因為使用官方,可以弱化和規避一些問題,比如: 版本迭代不同步,效能瓶頸,規範不統一等問題,也能快速支援官方的輔助工具。
-
2.正是由於官方的標準劃一,為自動化編譯,自動化搭建,測試調優,視覺化帶來便利,甚至為Ai前端模型化業務場景,提供支撐,都說前端的元件像搭
樂高
,Flutter
就是顆粒標準統一的樂高
。 -
3.天下之勢,分久必合,合久必分。
-
前端在經歷了
flash
一統pc頁面的富媒體時代,後被賈伯斯和H5瓦解; -
之後又有
H5
的繁盛和框架、語法、構建模式的亂戰; -
再到有“別再更新,老子學不動"的呼聲下,希望有個一統江湖的跨平臺系統出現;
-
讓開發者更專注於,提高業務內容,創造 “新, 酷,炫” 的展現形勢上下功夫。
-
也許,真有一個前端江湖的王者的誕生。
-
寫在最後
實際的 Flutter Widget
要複雜的更多的多,在眼花繚亂的 Widget
元件中,筆者想用自己的一些理解,去粗取精,來逐步理解 Flutter
這個新傢伙 ,文中有理解不到位的地方,歡迎大家指正。
筆者團隊也正在開發一套《Flutter GO》的APP,幫助大家熟悉複雜的 Flutter Widget
。
更多學習 Flutter的小夥伴,歡迎入QQ群 Flutter Go :679476515
《Flutter GO》專案地址 alibaba/flutter-go