Flutter快速上車之Widget

做者:閒魚技術-意境css

Flutter做爲一種全新的響應式,跨平臺,高性能的移動開發框架。從開源以來,已經獲得愈來愈多開發者的喜好。閒魚是最先一批與谷歌展開合做,並在重要的商品詳情頁中使用上線的公司。一路走來,積累了大量的開發經驗。雖然愈來愈多的技術大牛在flutter世界中弄得風聲水起,可是確定有不少的flutter小白但願能快速上手,享受flutter編程的樂趣。本文就是面向剛剛踏上futter的同窗,從Flutter體系中最基本的一個概念widget入手學習Flutter。但願能助力每一位初學者。html

可能你們要問的第一個問題是爲何從Widget開始?前端

從flutter的架構圖中不難看出widget是整個視圖描述的基礎。Flutter 的核心設計思想即是java

Everything’s a Widget
複製代碼

即一切即Widget。在flutter的世界裏,包括views,view controllers,layouts等在內的概念都創建在Widget之上。widget是flutter功能的抽象描述。因此掌握Flutter的基礎就是學會使用widget開始。android

本文會從你們熟悉的UI繪製視角來介紹flutter組件和佈局的基礎知識。首先羅列了UI開發中最爲經常使用,最爲基礎的組件。下面逐一進行介紹。css3

1 組件篇

1.1 Text

Text幾乎是UI開發中最爲重要的組件之一了,UI上面文字的展現基本上都要靠Text組件來完成。Flutter提供了原生的Text組件。Text的配置屬性是很豐富的,屬性主要分爲兩個部分一個是對齊&顯示控制相關的在Text類的屬性中,另外一類是樣式相關的屬性使用單獨的類TextStyle進行控制。跟native控件相比(以android爲例),Text的組件基本上提供了同等的能力,而且提供了更加豐富的樣式裝飾能力。詳細的屬性能夠參考官方文檔flutter text.web

1.1.1 實踐Coding

設置文字&文字大小&顏色&行數限制&文本對齊算法

const Text(  "hello flutter!",
            textAlign: TextAlign.center,
            maxLines: 1,
            overflow: TextOverflow.ellipsis, // 溢出顯示。。。
            style: TextStyle(fontSize: 30.0,// 文字大小
               color: Colors.yellow),// 文字顏色
          ),
複製代碼

效果以下:編程

1.2 Image

圖片也是UI部分開發最爲重要的組件之一。在能看圖隨看文字的年代,圖片是頁面展現的重中之重!Flutter一樣原生提供了Image組件。下面重點介紹一下幾個重點:緩存

1.2.1 縮放

怎樣設置圖片顯示的縮放方式呢? Flutter中的圖片縮放是fit字段來控制的。這是對最終圖片展現效果影響很大的一個參數,也是容易出錯的點。下面逐個分析一下flutter Image組件提供的縮放方式。

縮放屬性值在BoxFit枚舉中

下面列出的圖片是flutter官方對各類縮放作的圖片示例。基本上都表述很清楚了,就整理出來供你們查閱。

屬性 縮放效果
fill
contain
cover
fitWidth
fitHeight
none
scaleDown

1.2.2 圖片獲取

怎樣從各類來源加載圖片? 默認的Image組件不能直接顯示圖片,他須要一個ImageProvider來提供具體的圖片資源的(即Image中的image字段須要賦值)。咋一看這確實很是麻煩,可是實際上ImageProvider並不須要徹底從新本身實現。在Image類中目前提供了一下幾個實現好的ImageProvider,基本能知足常見的需求。

ImageProvider 用途
Image.asset 從asset資源文件中獲取圖片
Image.network 從網絡獲取圖片
Image.file 從本地file文件中獲取圖片
Image.memory 從內存中獲取圖片

Image一樣支持GIF圖片

網絡請求Image是你們最多見的操做。這裏重點說明兩個點:

  • 緩存

ImageCache是ImageProvider默認使用的圖片緩存。ImageCache使用的是LRU的算法。默承認以存儲1000張圖片。若是以爲緩存太大,能夠經過設置ImageCache的maximumSize屬性來控制緩存圖片的數量。也能夠經過設置maximumSizeBytes來控制緩存的大小(默認緩存大小10MB)。

  • CDN優化

若是想要使用cdn優化,能夠經過url增長後綴的方式實現。默認實現中沒有這個點,可是考慮到cdn優化的可觀收益,建議你們利用好這個優化。

1.2.3 FadeInImage

在實際開發中,考慮到圖片加載速度可能不能達到預期。因此但願能增長漸入效果&增長placeHolder的功能。Flutter一樣提供的這樣的組件——FadeInImage。

FadeInImage也提供了從多種渠道加載圖片的能力。這塊跟上面所說差別不大。這裏再也不贅述。

1.2.4 實踐Coding

  • 從網絡獲取圖片保持圖片比例並儘量大的放入
new Image.network(
            'https://gw.alicdn.com/tfs/TB1CgtkJeuSBuNjy1XcXXcYjFXa-906-520.png',
            fit: BoxFit.contain,
            width: 150.0,
            height: 100.0,
          ),
複製代碼
  • 效果以下:

1.3 Container

Flutter的設計思想就是徹底的widget化。這也就是說連最基本的padding,Center都是widget。設想一下若是每次寫view,連padding,Center都要本身包一個組件是一種怎樣的體驗?做爲一個工程師,別給只給我談思想,實際操做的操做效率也一樣很是重要。flutter 官方也意識到了這個問題,他們從實際編寫效率的角提供了一個友好高效的封裝,這就是Container!首先沒有任何疑問,Container 自己也是一個widget。可是他卻提供了對基礎widget的封裝,提升了UI基礎裝飾能力的表達效率。Container相似於android中的ViewGroup。

相信大部分的屬性你們都會感受很是親切,結合代碼註釋都比較容易理解,這裏就再也不贅述。其中須要重點解釋一下的是:Decoration和BoxConstraints。

1.3.1 裝飾

Decoration是對Container進行裝飾的描述。其概念相似與android中的shape。通常實際場景中會使用他的子類BoxDecoration。BoxDecoration提供了對背景色,邊框,圓角,陰影和漸變等功能的定製能力。 須要注意幾個點:

  • BoxDecoration的image屬性至關於設置的是背景圖。可是image會繪製在color 和gradient之上。
  • image是須要一個DecorationImage類的實現。DecorationImage的屬性和Image組件比較相似,能夠複用Image組件中的ImageProvider。

1.3.2 大小

BoxConstraints實際上是對Container組件大小的描述。BoxConstraints屬性比較簡單。若是不太清楚能夠研究一下盒子模型。這裏有個點須要重點說明一下:

  • 如何表達儘量大這樣的意思?(相似於android中的match_parent)Flutter中可使用double.infinity來作出相似的表達。

1.3.3 實踐Coding

  • 設置邊框&padding&margin&圓角&背景圖
new Container(
         alignment: Alignment.center,
         padding: const EdgeInsets.all(15.0),
         margin: const EdgeInsets.all(15.0),
         decoration: new BoxDecoration(
           border: new Border.all(
             color: Colors.red,
           ),
           image: const DecorationImage(
             image: const NetworkImage(
               'https://gw.alicdn.com/tfs/TB1CgtkJeuSBuNjy1XcXXcYjFXa-906-520.png',
             ),
             fit: BoxFit.contain,
           ),
           //borderRadius: const BorderRadius.all(const Radius.circular(6.0)),
           borderRadius: const BorderRadius.only(
             topLeft: const Radius.circular(3.0),
             topRight: const Radius.circular(6.0),
             bottomLeft: const Radius.circular(9.0),
             bottomRight: const Radius.circular(0.0),
           ),
         ),
         child: Text(''),
       ),
複製代碼
  • 效果以下:

1.4 手勢操做

手勢操做是最多見的UI交互操做。在Flutter中手勢識別也是一個widget!這點對新人來講又是一個新鮮的地方。一般來講能夠經過GestureDetector類來完成點擊事件的處理。使用時只須要將GestureDetector包裹在目標widget外面,再實現對應事件的函數便可。從點擊到長按,從縮放到拖動,這個類基本上都有相應的實現。具體能夠參見組件文檔。

2. 佈局

頁面佈局應該是UI編寫最爲根本的知識,其主要的描述的是父子組件子子組件之間的位置關係。首先咱們理解一下官方文檔的邏輯:

將佈局分爲單孩子和多孩子是Flutter佈局的一大特點。這點對native研發同窗來講會比較新鮮。單孩子組件主要繼承自SingleChildRenderObjectWidget。這些組件能提供豐富的裝飾能力(例如container),也能提供部分特定的佈局能力(例如center)。多孩子組件繼承自MultiChildRenderObjectWidget,能提供更加豐富的佈局能力(Flex,Stack,flow),但幾乎沒有裝飾的能力。下面介紹幾個重點佈局:

2.1 Flex

Flutter在佈局上也提供了完整的Flex佈局能力。可是在Flutter官方文檔中Layout Widgets,是看不到任何Flex的影子的。映入眼簾的倒是Row,Column,這些是什麼鬼?其實不難發現相似Row,Column 這樣的組件,他們的基類都是Flex。Row和Column差異是設置了不一樣的flex-direction。而之所這麼設計,是由於Flutter的widget從開始設計之初就考慮到UI佈局語義保持的重要性。這塊應該部分借鑑了前端的經驗,極力避免一個div搞定所有頁面的尷尬(固然flutter也可使用Flex來作一樣的事情,可是並不建議這麼作)。 Flutter使用的Flex模型基本上跟傳統的Css相似。這塊前端同窗能夠快速上手。可是Flex對於客戶端同窗來講是一種全新的佈局方式。Flex的基礎知識能夠參看flex佈局基礎。因爲篇幅有限這裏不展開敘述。這裏只重點強調一個點: 以下圖flex佈局概念以下:

flex經過direction設置了flex的主軸方向即main axis。和主軸垂直的方向叫作cross axis。flex佈局中對子佈局的控制是從main axis 和cross axis兩個方向上進行的。例如居中有main axis居中和cross axis居中。二者都居中才是容器的徹底居中。這點是客戶端同窗可能會容易弄混的地方。重點關注一下。

2.1.1 實踐Coding

ok,看完這些知識,咱們實際需求角度實際操做幾個case來熟悉一下Flex。

  • 居中
new Flex(direction: Axis.horizontal,
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
              new Container(
                  width: 40.0,
                  height: 60.0,
                  color: Colors.pink,
                  child: const Center(
                    child: const Text("left"),
                  )),
              new Container(
                  width: 80.0,
                  height: 60.0,
                  color: Colors.grey,
                  child: const Center(
                    child: const Text("middle"),
                  )),
              new Container(
                  width: 60.0,
                  height: 60.0,
                  color: Colors.yellow,
                  child: const Center(
                    child: const Text("right"),
                  )),
              ],
            ),
複製代碼
  • 效果以下:

  • weight left:right=2:1 經過設置Flexible的flex值大小完成比例設置
new Flex(
         direction: Axis.horizontal,
         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
         crossAxisAlignment: CrossAxisAlignment.center,
         children: <Widget>[
           new Flexible(
             flex: 2,
             fit: FlexFit.loose,
             child: new Container(
               color: Colors.blue,
               height: 60.0,
               alignment: Alignment.center,
               child: const Text('left!',
                   textAlign: TextAlign.center,
                   style: TextStyle(color: Colors.black),
                   textDirection: TextDirection.ltr),
             ),
           ),
           new Flexible(
             flex: 1,
             fit: FlexFit.loose,
             child: new Container(
               color: Colors.red,
               height: 60.0,
               alignment: Alignment.center,
               child: const Text('right',
                   textAlign: TextAlign.center,
                   style: TextStyle(color: Colors.black),
                   textDirection: TextDirection.ltr),
             ),
           ),
         ],
       )
複製代碼
  • 效果以下:

2.2 stack

在實際開發中,仍是須要在一些Widgets的上面再覆蓋上新的Widgets。這時候就須要層式佈局了。這種佈局在Native上,以android爲例,相似於relativeLayout 或者FrameLayout。在Flutter中使用的是Stack。

實際使用中Stack中的子Widgets分爲兩種:

  • positioned
    • 是包裹在組件Positioned中的組件
    • 能夠經過Positioned屬性靈活定位
  • non-positioned
    • 沒有包裹在Positioned組件中
    • 須要經過父Widget Stack 的屬性來控制佈局

對於non-positioned children, 咱們經過控制Stack的alignment屬性來控制對齊方式。Positioned的佈局方式相似於H5&weex中的position佈局中的absolute佈局方式。經過設置距離父組件上下左右的距離,Positioned對象能在Stack佈局中更加靈活的控制view的展示方式。

2.2.1 實踐Coding

  • 層疊佈局
new Container(
            color: Colors.yellow,
            height: 150.0,
            width: 500.0,
            child: new Stack(children: <Widget>[
              new Container(
                color: Colors.blueAccent,
                height: 50.0,
                width: 100.0,
                alignment: Alignment.center,
                child: Text('unPositioned'),
              ),
              new Positioned(
                  left: 40.0,
                  top: 80.0,
                  child: new Container(
                    color: Colors.pink,
                    height: 50.0,
                    width: 95.0,
                    alignment: Alignment.center,
                    child: Text('Positioned'),
                  )),
            ]))
複製代碼
  • 效果以下:

3. Visibility

當你看完Flutter Widge文檔的時候,咱們忽然發現一個略顯尷尬的問題:組件是否顯示怎麼控制?貌似全部的組件中都沒有這個屬性!這不坑了,咋辦?

目前看方法無非以下幾個:

3.1 刪除法

核心將該真實widget或者widget樹從renderTree中移除。

具體到實踐級別主要分爲兩個:

  • 單個組件‘隱藏’本身。在build方法中返回一個空的Container.
@override
Widget build(BuildContext context) {
  return isVisible
      ? Widget //真的Widget
      : new Container(); //空Widget 僅僅佔位 並不顯示
}
複製代碼
  • 多個child

在父容器的children字段的list中,刪除掉對應的cell。

3.2 Offstage

Offstage 是一個widget。Offstage的offstage屬性設置爲true,那麼Offstage以及他的child都將不會被繪製到界面上。 sample code以下:

@override
Widget build(BuildContext context) {
  return new Offstage(
          offstage: !isVisible,
          child:child);
}
複製代碼

3.3 透明度

設置widget的透明度,使之不可見。可是這樣的方法是反作用的。由於這個對應的widget樹是已經通過了完整的layout&paint過程,成本高。同時設置透明度自己也要耗費必定的計算資源,形成了二次浪費。須要注意的是即使變透明瞭,佔據的位置還在。你們慎重選擇使用。 sample code以下:

@override
Widget build(BuildContext context) {
  return new AnimatedOpacity(
        duration: Duration(milliseconds: 10),
        opacity: isVisible ? 1.0 : 0.0,
          child:child);
}
複製代碼

visibility的控制仍是比較麻煩的。這是Flutter設計上不符合正常習慣的一個點,須要你們重點關注。

4 生命週期

4.1 state 生命週期

widget是immutable的,發生變化的時候須要重建,因此談不上狀態。StatefulWidget 中的狀態保持實際上是經過State類來實現的。State擁有一套本身的生命週期,下面作一個簡單的介紹。

名稱 狀態
initState 插入渲染樹時調用,只調用一次
didChangeDependencies state依賴的對象發生變化時調用
didUpdateWidget 組件狀態改變時候調用,可能會調用屢次
build 構建Widget時調用
deactivate 當移除渲染樹的時候調用
dispose 組件即將銷燬時調用

生命週期狀態圖以下:

幾個注意點

  • didChangeDependencies有兩種狀況會被調用。

    • 建立時候在initState 以後被調用
    • 在依賴的InheritedWidget發生變化的時候會被調用
  • 正常的退出流程中會執行deactivate而後執行dispose。可是也會出現deactivate之後不執行dispose,直接加入樹中的另外一個節點的狀況。

  • 這裏的狀態改變包括兩種可能:1.經過setState內容改變 2.父節點的state狀態改變,致使孩子節點的同步變化。

4.2 App生命週期

須要指出的是若是想要知道App的生命週期,那麼須要經過WidgetsBindingObserver的didChangeAppLifecycleState 來獲取。經過該接口能夠獲取是生命週期在AppLifecycleState類中。經常使用狀態包含以下幾個:

名稱 狀態
resumed 可見並能相應用戶的輸入
inactive 處在並不活動狀態,沒法處理用戶相應
paused 不可見並不能相應用戶的輸入,可是在後臺繼續活動中

一個實際場景中的例子:

在不考慮suspending的狀況下:從後臺切入前臺生命週期變化以下:

  • AppLifecycleState.inactive->AppLifecycleState.resumed;

從前臺壓後臺生命週期變化以下:

  • AppLifecycleState.inactive->AppLifecycleState.paused;

5 初學者的困惑

5.1 爲何使用dart語言?

Dart語言對大部分開發者而言是很陌生的一種語言。google爲啥會選擇如此'冷門'的語言來開發flutter?主要緣由以下:

    1. dart具備jit&Aot雙重編譯執行方式。這樣就能利用JIt進行開發階段的hot reload開發,提高研發效率。同時在最終release版本中使用aot將dart代碼直接變成目標平臺的指令集代碼。簡單高效,最大限度保障了性能。
    1. dart針對flutter中頻繁建立銷燬Widget的場景作了專門的gc優化。經過分代無鎖垃圾回收器,將gc對性能的影響降至最低。
    1. dart語言在語法上面是類java的,易學易用。

5.2 爲何widget都是immutable?

我的認爲是兩個主要的點:

  • 提升渲染效率 flutte在頁面渲染上面的核心思想是simple is fast!將widget設計成immutable,因此在數據變化時,flutter選擇重建widget樹的方式進行數據更新。採用這樣方式的好處是框架不須要關心數據影響的範圍,簡單高效。缺點就是對GC會形成壓力。
  • 組件描述的複用 既然widget都是不可變的。那widget能夠以較低成本進行復用。在一個真實的渲染樹中可能存在同一個widget渲染樹中不一樣節點的狀況。

5.3 widget是view麼?

可能剛開始接觸flutter的同窗最疑惑的一個問題就是widget和view的關係了。那麼簡單分析一下: widget是對頁面UI的一種描述。他功能類有點似於android中的xml,或者web中的html。widget在渲染的時候會轉化成element。Element相比於widget增長了上下文的信息。element是對應widget,在渲染樹的實例化節點。因爲widget是immutable的,因此同一個widget能夠同時描述多個渲染樹中的節點。可是Element是描述固定在渲染書中的某一個特定位置的點。簡單點說widget做爲一種描述是能夠複用的,可是element卻跟須要繪製的節點一一對應。那element是最終渲染的view麼?抱歉,還不是。element繪製時會轉化成rendObject。RendObject纔是真正通過layout和paint並繪製在屏幕上的對象。在flutter中有三套渲染相關的tree,分別是:widget tree, element tree & rendObject tree。三者的渲染流程以下:

那可能有人會問,爲何須要增增長中間這層的Element tree?

flutter是響應式的框架。在某一時刻頁面的佈局,可能受不一樣的輸入源的影響。Element這層實際上作了對某一時刻事件的彙總,在將真正須要修改的部分同步到真實的rendObject tree上。這麼作有兩個好處:

  • 1.不須要直接操做UI,改成經過數據驅動視圖。代碼表達能夠更加精煉。
  • 2.最大層度下降對最終真實視圖(rendObject tree)的修改,提升頁面渲染效率。

5.4 StatelessWidget 和 StatefulWidget的區別

StatelessWidget是狀態不可變的widget。初始狀態設置之後就不可再變化。若是須要變化須要從新建立。StatefulWidget能夠保存本身的狀態。那問題是既然widget都是immutable的,怎麼保存狀態?其實Flutter是經過引入了State來保存狀態。當State的狀態改變時,能從新構建本節點以及孩子的Widget樹來進行UI變化。注意:若是須要主動改變State的狀態,須要經過setState()方法進行觸發,單純改變數據是不會引起UI改變的。

6.聯繫咱們

本文詳細解釋了基礎組件的用法,也解答了一些初學者的疑惑。但願能給剛踏上flutter學習之路的人一些幫助。若是對文本的內容有疑問或指正,歡迎告知咱們。

閒魚技術團隊是一隻短小精悍的工程技術團隊。咱們不只關注於業務問題的有效解決,同時咱們在推進打破技術棧分工限制(android/iOS/Html5/Server 編程模型和語言的統一)、計算機視覺技術在移動終端上的前沿實踐工做。做爲閒魚技術團隊的軟件工程師,您有機會去展現您全部的才能和勇氣,在整個產品的演進和用戶問題解決中證實技術發展是改變生活方式的動力。 簡歷投遞:guicai.gxy@alibaba-inc.com

7.引用:

相關文章
相關標籤/搜索