用前端 最舒服的躺姿 "搞定" Flutter (組件篇)

前言

要說2018年最火的跨端技術,當屬於 Flutter 莫屬,應該沒人質疑吧。一個新的技術的趨勢,最明顯的特徵,就是它必定想把「前浪」拍死在沙灘上。這個前浪,就是"react Native","weex"。目前隨便在搜索引擎上 搜索"Flutter reactNative",就全是這兩個技術的對比,評測。javascript

image.png

一股股濃濃 : 不服來 「掰」 啊 !!!的味道。

是的,錯過了react Native, weex 這些 「炸」 翻前端的技術,不能在錯過 Flutter 了,這年頭,你不會一門,跨端技術,怎麼好意思說本身是【前端】。css

timg.gif

Flutter是谷歌的移動UI框架,能夠快速在iOS和Android上構建高質量的原生用戶界面。 Flutter能夠與現有的代碼一塊兒工做。在全世界... ...
image.pnghtml

好了, 這些,你們早就知道了,來點實在的!!!

話說隔壁師兄,「閒魚」 是最先一批與谷歌展開合做,並在重要的商品詳情頁中使用flutter技術上線的BU。一路走來,積累了大量的開發經驗。「閒魚」 flutter 相關文章的都頗有深度,足見功力。前端

都是一篇篇的深度好文,讀完收益匪淺,可是對於剛接觸 Flutter 的web前端同窗來講仍是

image.pngimage.png2E803E03-8FE3-4F5B-9943-51F9C54B3BF1.png

好了,回到標題,筆者做爲一名傳統 web前端,想從前端最熟悉的視角 「躺」 着把 Flutter 瞭解一遍,不要敬仰,平視它!!!先從興趣開始。java

正文

Flutter 環境的搭建,其實有不少資源能夠參考。這裏就不累述了(知道有不少坑,在後續文章中,有機會把我的遇到的坑彙總一下 )。react

有興趣能夠參考 flutter安裝環境的搭建 , 在這裏建議各位,必定要本身親自搭一下環境,跑一下官方demo, 小馬過河,焉知深淺,本身定的位纔是最準確的。git

有了環境,先配置編輯器github

而後 建立一個Flutter項目web

項目建立完成,能夠先用 flutter run 跑一下。segmentfault

flutter run

好了,跑起來了吧,你會看到一個計數的官方示例,點擊加號圖片能夠作加運算。

這時候咱們看項目的project 目錄裏 有一個入口文件叫 main.dart。而後打開 main.dart 就像下面這樣:

image.png

( 爲何大家看到代碼比個人長,由於我摺疊了!!! ) 這不是重點,重點是每一個類繼承的都是一個尾號爲Widget 的字符。

聰明的你必定會以爲 WidgetFlutter 有着某種神祕的聯繫。

「Binggo!",是的,Flutter 有兩個重型武器,一個叫 Dart ,另外一個就是 Widget 了。

Dart 一切皆來自 Object, Flutter 的組件皆來自 Widget

關於強大的Dart,今天暫且不表,後面有時間能夠獨立篇幅來聊聊Dart。

先祭出一張 Flutter 的架構老圖。
image.png

Flutter 的世界裏,包括views,view controllers,layouts等在內的概念都創建在Widget之上。

WidgetFlutter 組件的抽象描述。因此掌握Flutter的基礎就是學會使用 Widget開始。

在Flutter界面渲染過程分爲三個階段:佈局、繪製、合成,佈局和繪製在Flutter框架中完成,合成則交由引擎負責:

640.jpeg

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 的概念和 FlutterWidget 是有相通性的。

「既然有react的概念,難道還有state,setState嗎?「

又對, Flutter 還真有 相似 state 狀態機制的概念,並且也確實有 setState 的方法。

「dome裏有兩個基類,StatelessWidget 和 StatefulWidget 是作什麼用的?」

StatelessWidget 和 StatefulWidget,這裏兩個類特別重要,幾乎全部的組件都是基於他們建立的。

StatelessWidget 是狀態不可變的widget,稱爲 無狀態widget。初始狀態設置之後就不可再變化。若是須要變化須要從新建立。

StatefulWidget 能夠保存本身的狀態,稱爲 有狀態widgetFlutter 首先保存了初始化時建立的State,狀態是經過改變State,來從新構建 Widget 樹來進行UI變化。改變狀態的方法,就是咱們用的最多的神器"setState",而單純改變數據是不會引起UI改變的,這個概念和咱們的 React 同樣同樣的。

若是你是初次接觸 Flutter 能夠不用記憶這麼多組件基類,只用記住如下式子就能夠, 不誇張的說,熟悉這個式子就能夠開發 Flutter 項目了:

c4d850e3c04dd51db6cad9112f885e6b.png

拆解

圍繞着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 之間的聯繫.
能夠參考這裏:
https://flutter.io/docs/get-s...
https://flutterchina.club/web...

筆者摘選其中樣例的重點部分,對比展現來講明( 因爲篇幅問題, 父子關係css 結構,用tab方式來表示 ):

文本樣式:

.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: <Color>[
            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: <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: <TextSpan>[
            new TextSpan(text: "Lorem "), // 繼承內聯樣式
            new TextSpan(
              text: "ipsum",
              style: new TextStyle( // 具備自定義樣式的單獨樣式
                fontWeight: FontWeight.w300,
                fontStyle: FontStyle.italic,
                fontSize: 48.0,
              ),
            ),
          ],
        ),
      ),
    ),
  )
);

組件化

官方的widgets目錄
image.png

點擊每個card後,裏面還有子card, 以一個展開的緯度來看,是這樣的:
image.png

這真是一個龐大的組件系統,這不是社區提供的,而是官方的。
flutter 團隊事無鉅細的實現了目前市面上基本上能見到的組件方式和類型。
我的認爲這樣作優缺點並存的。
先說缺點:

  • 1.學習成本增長,曲線也仍是比較陡峭的。
  • 2.組件直接的繼承關係路徑比較零亂,好比
    繼承自 Widget 是這些大類道還清晰:
    PreferredSizeWidget ProxyWidget RenderObjectWidget StatefulWidget StatelessWidget。
    可是,StatelessWidget和StatefulWidget的子類就過於平行化了,名稱上晦澀,沒有抽象架構化,分層或者塔型級別。
StatelessWidget:
1EB00739-AA5A-4939-8EE9-7EBB5EC2AAA3.png

StatefulWidget
image.png

  • 3.對自定義組件定義模糊,有這麼龐大的組件庫,到底之後是有個一個更系統的第三方組件庫去替換它,仍是說 Flutter 官方就不建議使用第三方組件,這個也未有定論。

再說優勢:

  • 1.能夠阻止之後輪子氾濫,在團隊僵持不下使用哪一個輪子庫時,最好的理由就是「官方」二字,由於使用官方,能夠弱化和規避一些問題,好比: 版本迭代不一樣步,性能瓶頸,規範不統一等問題,也能快速支持官方的輔助工具。
  • 2.正是因爲官方的標準劃一,爲自動化編譯,自動化搭建,測試調優,可視化帶來便利,甚至爲Ai前端模型化業務場景,提供支撐,都說前端的組件像搭樂高Flutter就是顆粒標準統一的樂高
  • 3.天下之勢,分久必合,合久必分。

    • 前端在經歷了 flash 一統pc頁面的富媒體時代,後被喬布斯和H5瓦解;
    • 以後又有H5 的繁盛和框架、語法、構建模式的亂戰;
    • 再到有「別再更新,老子學不動"的呼聲下,但願有個一統江湖的跨平臺系統出現;
    • 讓開發者更專一於,提升業務內容,創造 「新, 酷,炫」 的展示形勢上下功夫。
    • 也許,真有一個前端江湖的王者的誕生。

      image.png

寫在最後

實際的 Flutter Widget 要複雜的更多的多,在眼花繚亂的 Widget 組件中,筆者想用本身的一些理解,去粗取精,來逐步理解 Flutter 這個新傢伙 ,文中有理解不到位的地方,歡迎你們指正。
筆者團隊也正在開發一套《Flutter GO》的APP,幫助你們熟悉複雜的 Flutter Widget

原文連接

更多學習 Flutter的小夥伴,歡迎入QQ羣 Flutter Go :679476515

《Flutter GO》項目地址 alibaba/flutter-go

image.png

相關文章
相關標籤/搜索