【Flutter學習】頁面布局之基礎布局組件


一,概述  

  Flutter中擁有30多種預定義的布局widget,常用的有ContainerPaddingCenterFlexRowColumListViewGridView。按照《Flutter技術入門與實戰》上面來說的話,大概分為四類

  • 基礎布局組件Container(容器布局),Center(居中布局),Padding(填充布局),Align(對齊布局),Colum(垂直布局),Row(水平布局),Expanded(配合Colum,Row使用),FittedBox(縮放布局),Stack(堆疊布局),overflowBox(溢出父視圖容器)。
  • 寬高尺寸處理SizedBox(設置具體尺寸),ConstrainedBox(限定最大最小寬高布局),LimitedBox(限定最大寬高布局),AspectRatio(調整寬高比),FractionallySizedBox(百分比布局)
  • 列表和表格處理ListView(列表),GridView(網格),Table(表格)
  • 其它布局處理:Transform(矩陣轉換),Baseline(基准線布局),Offstage(控制是否顯示組件),Wrap(按寬高自動換行布局)

二,基礎布局處理組件

  • Container
    • 介紹:
      一個擁有繪制、定位、調整大小的widget,示意圖如下:
        
    • 組成

      Container的組成如下:

      • 最里層的是child元素;
      • child元素首先會被padding包着;
      • 然后添加額外的constraints限制;
      • 最后添加margin。

      Container的繪制的過程如下:

      • 首先會繪制transform效果;
      • 接着繪制decoration;
      • 然后繪制child;
      • 最后繪制foregroundDecoration。

      Container自身尺寸的調節分兩種情況:

      • Container在沒有子節點(children)的時候,會試圖去變得足夠大。除非constraints是unbounded限制,在這種情況下,Container會試圖去變得足夠小。
      • 帶子節點的Container,會根據子節點尺寸調節自身尺寸,但是Container構造器中如果包含了width、height以及constraints,則會按照構造器中的參數來進行尺寸的調節。
    • 布局行為

      由於Container組合了一系列的widget,這些widget都有自己的布局行為,因此Container的布局行為有時候是比較復雜的。

      一般情況下,Container會遵循如下順序去嘗試布局:

      • 對齊(alignment);
      • 調節自身尺寸適合子節點;
      • 采用width、height以及constraints布局;
      • 擴展自身去適應父節點;
      • 調節自身到足夠小。

      進一步說:

      • 如果沒有子節點、沒有設置width、height以及constraints,並且父節點沒有設置unbounded的限制,Container會將自身調整到足夠小。
      • 如果沒有子節點、對齊方式(alignment),但是提供了width、height或者constraints,那么Container會根據自身以及父節點的限制,將自身調節到足夠小。
      • 如果沒有子節點、width、height、constraints以及alignment,但是父節點提供了bounded限制,那么Container會按照父節點的限制,將自身調整到足夠大。
      • 如果有alignment,父節點提供了unbounded限制,那么Container將會調節自身尺寸來包住child;
      • 如果有alignment,並且父節點提供了bounded限制,那么Container會將自身調整的足夠大(在父節點的范圍內),然后將child根據alignment調整位置;
      • 含有child,但是沒有width、height、constraints以及alignment,Container會將父節點的constraints傳遞給child,並且根據child調整自身。

      另外,margin以及padding屬性也會影響到布局。

    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Container

      從繼承關系可以看出,Container是一個StatelessWidget。Container並不是一個最基礎的widget,它是由一系列的基礎widget組合而成。

    • 構造方法
      Container({
          Key key,
          this.alignment,
          this.padding, //設置內邊距
          Color color, //用來設置container背景色,如果foregroundDecoration設置的話,可能會遮蓋color效果。container背景色和decoration不能同時設置,
          Decoration decoration, //邊框、圓角、陰影、形狀、漸變、背景圖像
          this.foregroundDecoration, //decoration是背景,foregroundDecoration是前景。設置了foregroundDecoration可能會遮蓋child內容,一般半透明遮蓋(蒙層)效果使用!
          double width,
          double height,
          BoxConstraints constraints,
          this.margin, //設置外邊距,container與父邊框的距離
          this.transform,
          this.child, //孩子
        }) : assert(margin == null || margin.isNonNegative),
             assert(padding == null || padding.isNonNegative),
             assert(decoration == null || decoration.debugAssertIsValid()),
             assert(constraints == null || constraints.debugAssertIsValid()),
             assert(color == null || decoration == null,
               'Cannot provide both a color and a decoration\n'
               'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
             )
    • 參數解析
      key:Container唯一標識符,用於查找更新。
      alignment:控制child的對齊方式,如果container或者container父節點尺寸大於child的尺寸,這個屬性設置會起作用,有很多種對齊方式。
      padding:decoration內部的空白區域,如果有child的話,child位於padding內部。padding與margin的不同之處在於,padding是包含在content內,而margin則是外部邊界,設置點擊事件的話,padding區域會響應,而margin區域不會響應。
      color:用來設置container背景色,如果foregroundDecoration設置的話,可能會遮蓋color效果。
      decoration:繪制在child后面的裝飾,設置了decoration的話,就不能設置color屬性,否則會報錯,此時應該在decoration中進行顏色的設置。decoration可以設置邊框、背景色、背景圖片、圓角等屬性,非常實用。
      foregroundDecoration:繪制在child前面的裝飾。
      width:container的寬度,設置為double.infinity可以強制在寬度上撐滿,不設置,則根據child和父節點兩者一起布局。
      height:container的高度,設置為double.infinity可以強制在高度上撐滿。
      constraints:添加到child上額外的約束條件。
      margin:圍繞在decoration和child之外的空白區域,不屬於內容區域。
      transform:設置container的變換矩陣,類型為Matrix4。 對於transform這個屬性,一般有過其他平台開發經驗的,都大致了解,這種變換,一般不是變換的實際位置,而是變換的繪制效果,也就是說它的點擊以及尺寸、間距等都是按照未變換前的。
      child:container中的內容widget。
  • Center
    • 介紹:將其子widget居中顯示在自身內部的widget。只能有一個chlid,但是可以用container包含好多子child,繼承自Align。
      用於將其子項與其自身對齊,並根據子級的大小自行調整大小。示意圖:



    • 構造函數
      Center({ 
        Key key,
      double widthFactor, //寬度因子
      double heightFactor, //高度因子
      Widget child //
       }): super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
    • 參數解析
      widthFactor:寬度因子
      heightFactor:高度因子
      child:子節點/孩子/子組件
      (1)如果它的尺寸受到約束且[widthFactor]和[heightFactor]為空,則此窗口小部件將盡可能大。 
      (2)如果維度不受約束且相應的大小因子為null,則窗口小部件將匹配其在該維度中的子項大小(其實就是子view的寬高就是center容器的寬高)。
      (3)如果尺寸因子為非null,則此center容器的相應尺寸將是子view的尺寸和尺寸因子的乘積。
         例如,如果widthFactor是2.0,那么此小部件的寬度將始終是其子寬度的兩倍,並且將子view居中,來看看下圖吧。
  • Padding
    • 介紹

      Padding在Flutter中用的也挺多的,作為一個基礎的控件,功能非常單一,給子節點設置padding屬性。寫過其他端的都了解這個屬性,就是設置內邊距屬性,內邊距的空白區域,也是widget的一部分。

      Flutter中並沒有單獨的Margin控件,在Container中有margin屬性,看源碼關於margin的實現。

      if (margin != null)
        current = new Padding(padding: margin, child: current);

      不難看出,Flutter中淡化了margin以及padding的區別,margin實質上也是由Padding實現的。

      示意圖如下:
    • 布局行為

      Padding的布局分為兩種情況:

      • 當child為空的時候,會產生一個寬為left+right,高為top+bottom的區域;
      • 當child不為空的時候,Padding會將布局約束傳遞給child,根據設置的padding屬性,縮小child的布局尺寸。然后Padding將自己調整到child設置了padding屬性的尺寸,在child周圍創建空白區域。
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Padding

      從繼承關系可以看出,Padding控件是一個基礎控件,不像Container這種組合控件。Container中的margin以及padding屬性都是利用Padding控件去實現的。

      • 關於SingleChildRenderObjectWidget

        SingleChildRenderObjectWidget是RenderObjectWidgets的一個子類,用於限制只能有一個子節點。它只提供child的存儲,而不提供實際的更新邏輯。

      • 關於RenderObjectWidgets

        RenderObjectWidgets為RenderObjectElement提供配置,而RenderObjectElement包含着(wrap)RenderObject,RenderObject則是在應用中提供實際的繪制(rendering)的元素。
    • 構造函數
      Padding({
          Key key,
          @required this.padding, //內邊距
          Widget child,
        }) : assert(padding != null),
             super(key: key, child: child);
    • 參數含義  
      padding: 類型為EdgeInsetsGeometry  填充值可以使用EdgeInsets方法,例如:edgeInsets.all(6.0)將容器上下左右填充設置為6.0,也可以用EdgeInsets.only方法單獨設置某一邊的間距
  • Align
    • 介紹:在其他端的開發,Align一般都是當做一個控件的屬性,並沒有拿出來當做一個單獨的控件。Align本身實現的功能並不復雜,設置child的對齊方式,例如居中、居左居右等,並根據child尺寸調節自身尺寸。

      Align的布局行為分為兩種情況:

      • widthFactorheightFactor為null的時候,當其有限制條件的時候,Align會根據限制條件盡量的擴展自己的尺寸,當沒有限制條件的時候,會調整到child的尺寸;

      • widthFactor或者heightFactor不為null的時候,Aligin會根據factor屬性,擴展自己的尺寸,例如設置widthFactor為2.0的時候,那么,Align的寬度將會是child的兩倍。

         

      • Align為什么會有這樣的布局行為呢?
        原因很簡單,設置對齊方式的話,如果外層元素尺寸不確定的話,內部的對齊就無法確定。因此,會有寬高因子、根據外層限制擴大到最大尺寸、外層不確定時調整到child尺寸這些行為。

      • Center繼承自Align,只不過是將alignment設置為Alignment.center,其他屬性例如widthFactor、heightFactor,布局行為,都與Align完全一樣,在這里就不再單獨做介紹了。Center源碼如下,沒有設置alignment屬性,是因為Align默認的對齊方式就是居中。
    • 構造函數
      const Align({
          Key key,
          this.alignment: Alignment.center,
          this.widthFactor, //寬度因子
          this.heightFactor, //高度因子
          Widget child
        })
    • 參數的含義
      • alignment:對齊方式,一般會使用系統默認提供的9種方式,但是並不是說只有這9種,例如如下的定義。系統提供的9種方式只是預先定義好的。

        /// The top left corner.
        static const Alignment topLeft = const Alignment(-1.0, -1.0);

        Alignment實際上是包含了兩個屬性的,其中第一個參數,-1.0是左邊對齊,1.0是右邊對齊,第二個參數,-1.0是頂部對齊,1.0是底部對齊。根據這個規則,我們也可以自定義我們需要的對齊方式,例如

        /// 居右高於底部1/4處.
        static const Alignment rightHalfBottom = alignment: const Alignment(1.0, 0.5),

         

      • widthFactor:寬度因子,如果設置的話,Align的寬度就是child的寬度乘以這個值,不能為負數。

      • heightFactor:高度因子,如果設置的話,Align的高度就是child的高度乘以這個值,不能為負數。

  • Colum
    • 介紹
      Row和Column都是Flex的子類,只是direction參數不同。Column各方面同Row,可參考下面的Row

    • 構造函數
      Column({
          Key key,
          MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
          MainAxisSize mainAxisSize = MainAxisSize.max,
          CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
          TextDirection textDirection,
          VerticalDirection verticalDirection = VerticalDirection.down,
          TextBaseline textBaseline,
          List<Widget> children = const <Widget>[],
        }) : super(
          children: children,
          key: key,
          direction: Axis.vertical,
          mainAxisAlignment: mainAxisAlignment,
          mainAxisSize: mainAxisSize,
          crossAxisAlignment: crossAxisAlignment,
          textDirection: textDirection,
          verticalDirection: verticalDirection,
          textBaseline: textBaseline,
        );
    • 參數的含義
      (1) MainAxisSize:  控制自己的布局方式
      MainAxisSize.min  默認值,Column和Row自適應children;
      MainAxisSize.max  Column填充父控件豎屏,Row填充父控件橫屏;需要搭配MainAxisAlignment使用才有效果;
      (2) MainAxisAlignment:  控制子集的對齊方式,Column上下對齊,Row左右對齊
      MainAxisAlignment.start  默認值,Column靠上,Row靠左;
      MainAxisAlignment.center  Column,Row居中;
      MainAxisAlignment.end  Column靠下,Row靠右;
      MainAxisAlignment.spaceAround  自己填充,等份分配空間給子集,子集各自居中對齊;
      MainAxisAlignment.spaceBetween  自己填充,等份分配空間給子集,子集兩側對齊;
      MainAxisAlignment.spaceEvenly  自己填充,等份分配空間給子集,子集同一中線居中對齊;
              注:當設置MainAxisSize.max時才該值有效果。
       
      (3) CrossAxisAlignment:   控制子集 各自的對齊方式,Column左右對齊,Row上下對齊
      CrossAxisAlignment.strech        Column中會使子控件寬度調到最大,Row則使子控件高度調到最大
      CrossAxisAlignment.start      Column中會使子控件向左對齊,Row中會使子控件向上對齊
      CrossAxisAlignment.center   默認值,子控件居中
      CrossAxisAlignment.end    Column中會使子控件向右對齊,Row中會使子控件向下對齊
      CrossAxisAlignment.baseline  按文本水平線對齊。與TextBaseline搭配使用
      (4) TextBaseline
      TextBaseline.alphabetic    用於對齊字母字符底部的水平線。
      TextBaseline.ideographic  用於對齊表意字符的水平線。
      (5) VerticalDirection:  控制子控件對齊方式是否相反方式
      VerticalDirection.down  默認值,按照默認方式
      VerticalDirection.up   CrossAxisAlignment.start跟CrossAxisAlignment.end對反
  • Row
      • 介紹
        在Flutter中非常常見的一個多子節點控件,將children排列成一行。估計是借鑒了Web中Flex布局,所以很多屬性和表現,都跟其相似。但是注意一點,自身不帶滾動屬性,如果超出了一行,在debug下面則會顯示溢出的提示。

      • 布局行為:Row的布局有六個步驟,這種布局表現來自Flex(Row和Column的父類):
        1. 首先按照不受限制的主軸(main axis)約束條件,對flex為null或者為0的child進行布局,然后按照交叉軸( cross axis)的約束,對child進行調整;
        2. 按照不為空的flex值,將主軸方向上剩余的空間分成相應的幾等分;
        3. 對上述步驟flex值不為空的child,在交叉軸方向進行調整,在主軸方向使用最大約束條件,讓其占滿步驟2所分得的空間;
        4. Flex交叉軸的范圍取自子節點的最大交叉軸;
        5. 主軸Flex的值是由mainAxisSize屬性決定的,其中MainAxisSize可以取max、min以及具體的value值;
        6. 每一個child的位置是由mainAxisAlignment以及crossAxisAlignment所決定。

          Row的布局行為表面上看有這么多個步驟,其實也還算是簡單,可以完全參照web中的Flex布局,包括主軸、交叉軸等概念。

          
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row

      Row以及Column都是Flex的子類,它們的具體實現也都是由Flex完成,只是參數不同。

    • 構造函數
      Row({
        Key key,
        MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
        MainAxisSize mainAxisSize = MainAxisSize.max,
        CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
        TextDirection textDirection,
        VerticalDirection verticalDirection = VerticalDirection.down,
        TextBaseline textBaseline,
        List<Widget> children = const <Widget>[],
      })
    • 參數的含義

      MainAxisAlignment:主軸方向上的對齊方式,會對child的位置起作用,默認是start

      其中MainAxisAlignment枚舉值:

      • center:將children放置在主軸的中心;
      • end:將children放置在主軸的末尾;
      • spaceAround:將主軸方向上的空白區域均分,使得children之間的空白區域相等,但是首尾child的空白區域為1/2;
      • spaceBetween:將主軸方向上的空白區域均分,使得children之間的空白區域相等,首尾child都靠近首尾,沒有間隙;
      • spaceEvenly:將主軸方向上的空白區域均分,使得children之間的空白區域相等,包括首尾child;
      • start:將children放置在主軸的起點;

      其中spaceAround、spaceBetween以及spaceEvenly的區別,就是對待首尾child的方式。其距離首尾的距離分別是空白區域的1/2、0、1。

      MainAxisSize:在主軸方向占有空間的值,默認是max。

      MainAxisSize的取值有兩種:

      • max:根據傳入的布局約束條件,最大化主軸方向的可用空間;
      • min:與max相反,是最小化主軸方向的可用空間;

      CrossAxisAlignment:children在交叉軸方向的對齊方式,與MainAxisAlignment略有不同。

      CrossAxisAlignment枚舉值有如下幾種:

      • baseline:在交叉軸方向,使得children的baseline對齊;
      • center:children在交叉軸上居中展示;
      • end:children在交叉軸上末尾展示;
      • start:children在交叉軸上起點處展示;
      • stretch:讓children填滿交叉軸方向;

      TextDirection:阿拉伯語系的兼容設置,一般無需處理。

      VerticalDirection:定義了children擺放順序,默認是down。

      VerticalDirection枚舉值有兩種:

      • down:從top到bottom進行布局;
      • up:從bottom到top進行布局。

      top對應Row以及Column的話,就是左邊和頂部,bottom的話,則是右邊和底部。

      TextBaseline:使用的TextBaseline的方式,有兩種,前面已經介紹過。

  • Expanded
    • 介紹
      Expanded組件可以使 RowColumnFiex等子組件在其主軸上方向展開並填充可用的空間,這里注意: Expanded組件必須用在 RowColumnFiex內,並且從 Expanded到封裝它的 RowColumnFlex的路徑必須只包括 StatelessWidgets或者 StatefulWidgets(不能是其他類型的組件,像 RenderObjectWidget,它是渲染對象,不再改變尺寸,因此 Expanded不能放進 RenderObjectWidget),示意圖如下:

      注意一點:在Row中使用Expanded的時候,無法指定Expanded中的子組件的寬度width,但可以指定其高度height。同理,在Column中使用Expanded的時候,無法指定Expanded中的子組件的高度height,可以指定寬度width。

    • 構造函數
       const Expanded({
          Key key,
          int flex = 1,
          @required Widget child,
        }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child);
  • FittedBox
    • 介紹:

      按照其官方的介紹,它主要做了兩件事情,縮放(Scale)以及位置調整(Position)。

      FittedBox會在自己的尺寸范圍內縮放並且調整child位置,使得child適合其尺寸。做過移動端的,可能會聯想到ImageView控件,它是將圖片在其范圍內,按照規則,進行縮放位置調整。FittedBox跟ImageView是有些類似的,可以猜測出,它肯定有一個類似於ScaleType的屬性。

    • 布局行為:

      FittedBox的布局行為還算簡單,官方沒有給出說明,我在這里簡單說一下。由於FittedBox是一個容器,需要讓其child在其范圍內縮放,因此其布局行為分兩種情況:

      • 如果外部有約束的話,按照外部約束調整自身尺寸,然后縮放調整child,按照指定的條件進行布局;
      • 如果沒有外部約束條件,則跟child尺寸一致,指定的縮放以及位置屬性將不起作用。
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > FittedBox
    • 構造函數
      const FittedBox({
      Key key,
      this.fit: BoxFit.contain,
      this.alignment: Alignment.center,
      Widget child,
      })
    • 參數的含義

      fit:縮放的方式,默認的屬性是BoxFit.contain,child在FittedBox范圍內,盡可能的大,但是不超出其尺寸。這里注意一點,contain是保持着child寬高比的大前提下,盡可能的填滿,一般情況下,寬度或者高度達到最大值時,就會停止縮放。

      alignment:對齊方式,默認的屬性是Alignment.center,居中顯示child。

    • 使用場景
      FittedBox在目前的項目中還未用到過。對於需要縮放調整位置處理的,一般都是圖片。筆者一般都是使用Container中的decoration屬性去實現相應的效果。對於其他控件需要縮放以及調整位置的,目前還沒有遇到使用場景,大家只需要知道有這么一個控件,可以實現這個功能即可。
  • Stack
      • 介紹:
        Stack可以類比web中的absolute,絕對布局。絕對布局一般在移動端開發中用的較少,但是在某些場景下,還是有其作用。當然,能用Stack絕對布局完成的,用其他控件組合也都能實現。
      • 布局行為

    Stack的布局行為,根據child是positioned還是non-positioned來區分。

        • 對於positioned的子節點,它們的位置會根據所設置的top、bottom、right以及left屬性來確定,這幾個值都是相對於Stack的左上角;
        • 對於non-positioned的子節點,它們會根據Stack的aligment來設置位置。

    對於繪制child的順序,則是第一個child被繪制在最底端,后面的依次在前一個child的上面,類似於web中的z-index。如果想調整顯示的順序,則可以通過擺放child的順序來進行。

      • 繼承關系
        Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Stack
      • 構造函數
        Stack({
          Key key,
          this.alignment = AlignmentDirectional.topStart,
          this.textDirection,
          this.fit = StackFit.loose,
          this.overflow = Overflow.clip,
          List<Widget> children = const <Widget>[],
        })
      • 參數的含義

    alignment:對齊方式,默認是左上角(topStart)。

    textDirection:文本的方向,絕大部分不需要處理。

    fit:定義如何設置non-positioned節點尺寸,默認為loose。

    其中StackFit有如下幾種:

        • loose:子節點寬松的取值,可以從min到max的尺寸;
        • expand:子節點盡可能的占用空間,取max尺寸;
        • passthrough:不改變子節點的約束條件。

    overflow:超過的部分是否裁剪掉(clipped)。

  • IndexedStack
    • 介紹
      IndexedStack繼承自Stack,它的作用是顯示第index個child,其他child都是不可見的。所以IndexedStack的尺寸永遠是跟最大的子節點尺寸一致。
    • 構造函數
        IndexedStack({
          Key key,
          AlignmentGeometry alignment = AlignmentDirectional.topStart,
          TextDirection textDirection,
          StackFit sizing = StackFit.loose,
          this.index = 0,
          List<Widget> children = const <Widget>[],
        }) : super(key: key, alignment: alignment, textDirection: textDirection, fit: sizing, children: children);
    • 參數的含義

  • OverflowBox
    • 介紹:
      OverflowBox這個控件,允許child超出parent的范圍顯示,當然不用這個控件,也有很多種方式實現類似的效果。
    • 布局行為
      當OverflowBox的最大尺寸大於child的時候,child可以完整顯示,當其小於child的時候,則以最大尺寸為基准,當然,這個尺寸都是可以突破父節點的。最后加上對齊方式,完成布局。
    • 繼承關系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > OverflowBox
    • 構造函數
      const OverflowBox({
          Key key,
          this.alignment = Alignment.center,
          this.minWidth,
          this.maxWidth,
          this.minHeight,
          this.maxHeight,
          Widget child,
        })
    • 參數的含義

      alignment:對齊方式。

      minWidth:允許child的最小寬度。如果child寬度小於這個值,則按照最小寬度進行顯示。

      maxWidth:允許child的最大寬度。如果child寬度大於這個值,則按照最大寬度進行展示。

      minHeight:允許child的最小高度。如果child高度小於這個值,則按照最小高度進行顯示。

      maxHeight:允許child的最大高度。如果child高度大於這個值,則按照最大高度進行展示。

      其中,最小以及最大寬高度,如果為null的時候,就取父節點的constraint代替。

    • 使用場景
      有時候設計圖上出現的角標,會超出整個模塊,可以使用OverflowBox控件。但我們應該知道,不使用這種控件,也可以完成布局,在最外面包一層,也能達到一樣的效果。具體實施起來哪個比較方便,同學們自行取舍。

三,常用示例

  • Container
    /**
     * Container 組件
     * 
     */
    class MyContainer extends StatelessWidget  {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Center(
          child:new Container(
            child: new Text('Hellow,Flutter——Container'),
            //定位
            padding: const EdgeInsets.all(8.0),
            alignment: Alignment.center,
            transform: new Matrix4.rotationZ(0.3),
            //繪制
            decoration: new BoxDecoration(
              //邊框
              border: new Border.all(
                color: Colors.red,
                width: 5.0
              ),
    //背景顏色 color: Colors.grey,
    //圓角 borderRadius:
    new BorderRadius.all(new Radius.circular(20.0)), image: new DecorationImage( image: new NetworkImage('http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), centerSlice: new Rect.fromLTRB(270.0, 280.0, 1360.0, 730.0), ), ), //尺寸 width: 200, height: 200, ) , ) ; } }

    效果圖


    源碼解析

    decoration = decoration ?? (color != null ? new BoxDecoration(color: color) : null),

    可以看出,對於顏色的設置,最后都是轉換為decoration來進行繪制的。如果同時包含decoration和color兩種屬性,則會報錯。

    @override
    Widget build(BuildContext context) {
    Widget current = child;
    
    if (child == null && (constraints == null || !constraints.isTight)) {
      current = new LimitedBox(
      maxWidth: 0.0,
      maxHeight: 0.0,
      child: new ConstrainedBox(constraints: const BoxConstraints.expand())
     );
    }
    
    if (alignment != null)
     current = new Align(alignment: alignment, child: current);
    
    final EdgeInsetsGeometry effectivePadding = _paddingIncludingDecoration;
    if (effectivePadding != null)
     current = new Padding(padding: effectivePadding, child: current);
    
    if (decoration != null)
     current = new DecoratedBox(decoration: decoration, child: current);
    
    if (foregroundDecoration != null) {
       current = new DecoratedBox(
       decoration: foregroundDecoration,
       position: DecorationPosition.foreground,
       child: current
      );
    }
    
    if (constraints != null)
      current = new ConstrainedBox(constraints: constraints, child: current);
    
    if (margin != null)
      current = new Padding(padding: margin, child: current);
    
    if (transform != null)
      current = new Transform(transform: transform, child: current);
    
     return current;
    }

    Container的build函數不長,繪制也是一個線性的判斷的過程,一層一層的包裹着widget,去實現不同的樣式。

    最里層的是child,如果為空或者其他約束條件,則最里層包含的為一個LimitedBox,然后依次是Align、Padding、DecoratedBox、前景DecoratedBox、ConstrainedBox、Padding(實現margin效果)、Transform。

    Container的源碼本身並不復雜,復雜的是它的各種布局表現。我們謹記住一點,如果內部不設置約束,則按照父節點盡可能的擴大,如果內部有約束,則按照內部來。

  • Center
    /**
     * Center
     */
    
    class MyCenter extends StatelessWidget {
      //不用center組件
      Widget text = new Text(
          '不包含center'
      );
      //包含center組件
      Widget center = new Center(
        child: new Text(
          '包含center組件'
        ),
      );
    
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return text;
      }
    }

    效果圖


    源碼解析

    Center繼承自Align,只不過是將alignment設置為Alignment.center,其他屬性例如widthFactor、heightFactor,布局行為,都與Align完全一樣。Center源碼如下,沒有設置alignment屬性,是因為Align默認的對齊方式就是居中。

    class Center extends Align {
    /// Creates a widget that centers its child.
    const Center({ Key key, double widthFactor, double heightFactor, Widget child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
    }
  • Padding
    /**
     * Padding
     */
    
    class MyPadding extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Padding(
          padding: new EdgeInsets.all(8.0),
          child: const Card(
            child: const Text('Flutter布局組件--Padding'),
          ),
        );
      }
    }

    效果圖

    源碼解析:
    @override
     RenderPadding createRenderObject(BuildContext context) {
     return new RenderPadding(
       padding: padding,
       textDirection: Directionality.of(context),
     );
    }

    Padding的創建函數,實際上是由RenderPadding來進行的。

    關於RenderPadding的實際布局表現,當child為null的時候:

    if (child == null) {
     size = constraints.constrain(new Size(
     _resolvedPadding.left + _resolvedPadding.right,
     _resolvedPadding.top + _resolvedPadding.bottom
    ));
    return;
    }

    返回一個寬為_resolvedPadding.left+_resolvedPadding.right,高為_resolvedPadding.top+_resolvedPadding.bottom的區域。

    當child不為null的時候,經歷了三個過程,即調整child尺寸、調整child位置以及調整Padding尺寸,最終達到實際的布局效果。

    // 調整child尺寸
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
    child.layout(innerConstraints, parentUsesSize: true);
    
    // 調整child位置
    final BoxParentData childParentData = child.parentData;
    childParentData.offset = new Offset(_resolvedPadding.left, _resolvedPadding.top);
    
    // 調整Padding尺寸
    size = constraints.constrain(new Size(
    _resolvedPadding.left + child.size.width + _resolvedPadding.right,
    _resolvedPadding.top + child.size.height + _resolvedPadding.bottom
    ));

    到此處,上面介紹的padding布局行為就解釋的通了。

  • Align
    /**
     * Align
     * 設置一個寬高為child兩倍區域的Align,其child處在正中間。
     */
    class MyAlign extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Align(
          alignment: Alignment.center,
          widthFactor: 2.0,
          heightFactor: 2.0,
          child: new Text("flutter布局組件之Align"),
        );
      }
    }

    效果圖



    源碼解析:

    @override
    RenderPositionedBox createRenderObject(BuildContext context) {
    return new RenderPositionedBox(
    alignment: alignment,
    widthFactor: widthFactor,
    heightFactor: heightFactor,
    textDirection: Directionality.of(context),
    );
    }

    Align的實際構造調用的是RenderPositionedBox。

    RenderPositionedBox的布局表現如下:

    // 根據_widthFactor、_heightFactor以及限制因素來確定寬高
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
    
    if (child != null) {
    // 如果child不為null,則根據規則設置Align的寬高,如果需要縮放,則根據_widthFactor是否為null來進行縮放,如果不需要,則盡量擴展。
    child.layout(constraints.loosen(), parentUsesSize: true);
    size = constraints.constrain(new Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
    shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
    alignChild();
    } else {
    // 如果child為null,如果需要縮放,則變為0,否則就盡量擴展
    size = constraints.constrain(new Size(shrinkWrapWidth ? 0.0 : double.infinity,
    shrinkWrapHeight ? 0.0 : double.infinity));
    }
  • Colum
    /**
     * Column
     * 使用Expanded控件,將一行的寬度分成四個等分,第一、三個child占1/4的區域,第二個child占1/2區域,由flex屬性控制。
     */
    
    class MyColumn extends StatelessWidget  {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Column(
          children: <Widget>[
            new Expanded(
              child: new Container(
                color: Colors.red,
                padding: EdgeInsets.all(5.0),
              ),
              flex: 1,
            ),
            new Expanded(
              child: new Container(
                color: Colors.yellow,
                padding: EdgeInsets.all(5.0),
              ),
              flex: 2,
            ),
            new Expanded(
              child: new Container(
                color: Colors.blue,
                padding: EdgeInsets.all(5.0),
              ),
              flex: 1,
            )
          ],
        );
      }
    }

    效果圖

    源碼解析
       和Row類似,參考Row
  • Row
    /**
     * Row
     *  使用Expanded控件,將水平方向一行的寬度分成四個等分,第一、三個child占1/4的區域,第二個child占1/2區域,由flex屬性控制。
     */
    class MyRow extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Row(
          children: <Widget>[
            new Expanded(
              flex: 1,
              child: new Container(
                color: Colors.red,
                padding: EdgeInsets.all(5.0),
              ),
            ),
            new Expanded(
              flex: 2,
              child: new Container(
                color: Colors.yellow,
                padding: EdgeInsets.all(5.0),
              ) ,
            ),
            new Expanded(
              flex: 1,
              child: new Container(
                color: Colors.blue,
                padding: EdgeInsets.all(5.0),
              ),
            )
          ],
        );
      }
    }

    效果圖



    原理圖

    Row以及Column的源代碼就一個構造函數,具體的實現全部在它們的父類Flex中。

    關於Flex的構造函數

    Flex({
    Key key,
    @required this.direction,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.mainAxisSize = MainAxisSize.max,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    this.textBaseline,
    List<Widget> children = const <Widget>[],
    })

    可以看出,Flex的構造函數就比Row和Column的多了一個參數。Row跟Column的區別,正是這個direction參數的不同。當為Axis.horizontal的時候,則是Row,當為Axis.vertical的時候,則是Column。

    我們來看下Flex的布局函數,由於布局函數比較多,因此分段來講解:

    while (child != null) {
     final FlexParentData childParentData = child.parentData;
     totalChildren++;
     final int flex = _getFlex(child);
    if (flex > 0) {
      totalFlex += childParentData.flex;
      lastFlexChild = child;
    } else {
     BoxConstraints innerConstraints;
     if (crossAxisAlignment == CrossAxisAlignment.stretch) {
      switch (_direction) {
       case Axis.horizontal:
        innerConstraints = new BoxConstraints(minHeight: constraints.maxHeight,
        maxHeight: constraints.maxHeight);
        break;
       case Axis.vertical:
        innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
        maxWidth: constraints.maxWidth);
       break;
     }
    } else {
     switch (_direction) {
      case Axis.horizontal:
        innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
       break;
      case Axis.vertical:
      innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
       break;
     }
    }
    child.layout(innerConstraints, parentUsesSize: true);
    allocatedSize += _getMainSize(child);
    crossSize = math.max(crossSize, _getCrossSize(child));
    }
    child = childParentData.nextSibling;
    }

    上面這段代碼,我把中間的一些assert以及錯誤信息之類的代碼剔除了,不影響實際的理解。

    在布局的開始,首先會遍歷一遍child,遍歷的作用有兩點:

    對於存在flex值的child,計算出flex的和,找到最后一個包含flex值的child。找到這個child,是因為主軸對齊方式,可能會對它的位置做調整,需要找出來;
    對於不包含flex的child,根據交叉軸方向的設置,對child進行調整。

    final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
    if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
     final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
     child = firstChild;
    while (child != null) {
     final int flex = _getFlex(child);
    if (flex > 0) {
     final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
    double minChildExtent;
    switch (_getFit(child)) {
     case FlexFit.tight:
      assert(maxChildExtent < double.infinity);
      minChildExtent = maxChildExtent;
      break;
     case FlexFit.loose:
      minChildExtent = 0.0;
      break;
    }
    BoxConstraints innerConstraints;
    if (crossAxisAlignment == CrossAxisAlignment.stretch) {
    switch (_direction) {
     case Axis.horizontal:
      innerConstraints = new BoxConstraints(minWidth: minChildExtent,
      maxWidth: maxChildExtent,
      minHeight: constraints.maxHeight,
      maxHeight: constraints.maxHeight);
     break;
     case Axis.vertical:
      innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth,
      maxWidth: constraints.maxWidth,
      minHeight: minChildExtent,
      maxHeight: maxChildExtent);
      break;
     }
    } else {
    switch (_direction) {
     case Axis.horizontal:
      innerConstraints = new BoxConstraints(minWidth: minChildExtent,
      maxWidth: maxChildExtent,
      maxHeight: constraints.maxHeight);
     break;
    case Axis.vertical:
     innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth,
     minHeight: minChildExtent,
     maxHeight: maxChildExtent);
     break;
     }
    }
    child.layout(innerConstraints, parentUsesSize: true);
    final double childSize = _getMainSize(child);
    allocatedSize += childSize;
    allocatedFlexSpace += maxChildExtent;
    crossSize = math.max(crossSize, _getCrossSize(child));
    }
    if (crossAxisAlignment == CrossAxisAlignment.baseline) {
    final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
    if (distance != null)
    maxBaselineDistance = math.max(maxBaselineDistance, distance);
    }
    final FlexParentData childParentData = child.parentData;
    child = childParentData.nextSibling;
    }
    }

    上面的代碼段所做的事情也有兩點:

    (1)為包含flex的child分配剩余的空間
    (2)對於每份flex所對應的空間大小,它的計算方式如下:

    final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
    final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
    其中,allocatedSize是不包含flex所占用的空間。當每一份flex所占用的空間計算出來后,則根據交叉軸的設置,對包含flex的child進行調整。

    計算出baseline值
    如果交叉軸的對齊方式為baseline,則計算出最大的baseline值,將其作為整體的baseline值。

    switch (_mainAxisAlignment) {
    case MainAxisAlignment.start:
    leadingSpace = 0.0;
    betweenSpace = 0.0;
    break;
    case MainAxisAlignment.end:
    leadingSpace = remainingSpace;
    betweenSpace = 0.0;
    break;
    case MainAxisAlignment.center:
    leadingSpace = remainingSpace / 2.0;
    betweenSpace = 0.0;
    break;
    case MainAxisAlignment.spaceBetween:
    leadingSpace = 0.0;
    betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
    break;
    case MainAxisAlignment.spaceAround:
    betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
    leadingSpace = betweenSpace / 2.0;
    break;
    case MainAxisAlignment.spaceEvenly:
    betweenSpace = totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
    leadingSpace = betweenSpace;
    break;
    }

    然后,就是將child在主軸方向上按照設置的對齊方式,進行位置調整。上面代碼就是計算前后空白區域值的過程,可以看出spaceBetween、spaceAround以及spaceEvenly的差別。

    double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
    child = firstChild;
    while (child != null) {
    final FlexParentData childParentData = child.parentData;
    double childCrossPosition;
    switch (_crossAxisAlignment) {
    case CrossAxisAlignment.start:
    case CrossAxisAlignment.end:
    childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
    == (_crossAxisAlignment == CrossAxisAlignment.start)
    ? 0.0
    : crossSize - _getCrossSize(child);
    break;
    case CrossAxisAlignment.center:
    childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
    break;
    case CrossAxisAlignment.stretch:
    childCrossPosition = 0.0;
    break;
    case CrossAxisAlignment.baseline:
    childCrossPosition = 0.0;
    if (_direction == Axis.horizontal) {
    assert(textBaseline != null);
    final double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true);
    if (distance != null)
    childCrossPosition = maxBaselineDistance - distance;
    }
    break;
    }
    if (flipMainAxis)
    childMainPosition -= _getMainSize(child);
    switch (_direction) {
    case Axis.horizontal:
    childParentData.offset = new Offset(childMainPosition, childCrossPosition);
    break;
    case Axis.vertical:
    childParentData.offset = new Offset(childCrossPosition, childMainPosition);
    break;
    }
    if (flipMainAxis) {
    childMainPosition -= betweenSpace;
    } else {
    childMainPosition += _getMainSize(child) + betweenSpace;
    }
    child = childParentData.nextSibling;
    }

    最后,則是根據交叉軸的對齊方式設置,對child進行位置調整,到此,布局結束。

    我們可以順一下整體的流程:

    計算出flex的總和,並找到最后一個設置了flex的child;
    對不包含flex的child,根據交叉軸對齊方式,對齊進行調整,並計算出主軸方向上所占區域大小;
    計算出每一份flex所占用的空間,並根據交叉軸對齊方式,對包含flex的child進行調整;
    如果交叉軸設置為baseline對齊,則計算出整體的baseline值;
    按照主軸對齊方式,對child進行調整;
    最后,根據交叉軸對齊方式,對所有child位置進行調整,完成布局。

  • Expanded
    class MyHomePage extends StatelessWidget {
      ....
      body:new RowWidget(),
      ...
    }
    class RowWidget extends StatelessWidget{
      @override
      Widget build(BuildContext context){
        return Row(
            children: <Widget>[
              new RaisedButton(
                  onPressed: (){
    
                  },
                  color:Colors.green,
                  child:new Text('綠色按鈕1')
              ),
              new Expanded(
                child:new RaisedButton(
                  onPressed: (){
    
                  },
                  color:Colors.yellow,
                  child:new Text('黃色按鈕2')
                ),
              ),
              new RaisedButton(
                  onPressed:(){
    
                  },
                  color:Colors.red,
                  child:new Text('黑色按鈕3')),
          ],
        );
      }
    }

    效果圖:

  • FittedBox
    /**
     * Fitted Box
     * 加入Container是為了加顏色顯示兩個區域,讀者可以試着修改fit以及alignment查看其不同的效果。
     * 類似於其它移動端的imageView的contentView屬性
     */
    
    class MyFittedBox extends StatelessWidget  {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Container(
          width: 300.0,
          height: 300.0,
          color: Colors.blue,
          child: new FittedBox(
            fit: BoxFit.contain,
            alignment: Alignment.topLeft,
            child: new Container(
              color: Colors.red,
              child: new Text('FittedBox'),
            ),
          ),
        );
      }
    }

    效果圖

    源碼解析

    @override
    RenderFittedBox createRenderObject(BuildContext context) {
     return new RenderFittedBox(
      fit: fit,
      alignment: alignment,
      textDirection: Directionality.of(context),
     );
    }

    FittedBox具體實現是由RenderFittedBox進行的。不知道讀者有沒有發現,目前的一些基礎控件,繼承自RenderObjectWidget的,widget本身都只是存儲了一些配置信息,真正的繪制渲染,則是由內部的createRenderObject所調用的RenderObject去實現的。

    RenderFittedBox具體的布局代碼如下:

    if (child != null) {
      child.layout(const BoxConstraints(), parentUsesSize: true);
      // 如果child不為null,則按照child的尺寸比率縮放child的尺寸
      size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child.size);
      _clearPaintData();
     } else {
      // 如果child為null,則按照最小尺寸進行布局
      size = constraints.smallest;
    }
  • Stack
    /**
     * Stack
     */
    
    class MyStack extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return new Stack(
          alignment: const Alignment(0.6, 0.6),
          children: <Widget>[
            new CircleAvatar(
              backgroundImage: AssetImage('a'),
              radius: 100.0,
            ),
            new Container(
              decoration: BoxDecoration(
                color: Colors.black45,
              ),
              child: new Text(
                'FLutter-Statck',
                style:new TextStyle(
                  fontSize:20.0,
                  fontWeight:FontWeight.bold,
                  color:Colors.white
                ),
              ),
            )
          ],
        );
      }
    }

    效果圖

  源碼解析

  (1)Stack的布局代碼有些長,在此分段進行講解。

     如果不包含子節點,則尺寸盡可能大。

if (childCount == 0) {
  size = constraints.biggest;
  return;
}

      (2)根據fit屬性,設置non-positioned子節點約束條件。

switch (fit) {
 case StackFit.loose:
   nonPositionedConstraints = constraints.loosen();
   break;
 case StackFit.expand:
   nonPositionedConstraints = new BoxConstraints.tight(constraints.biggest);
   break;
 case StackFit.passthrough:
   nonPositionedConstraints = constraints;
   break;
}

   (3)對non-positioned子節點進行布局。

RenderBox child = firstChild;
while (child != null) {
final StackParentData childParentData = child.parentData;
if (!childParentData.isPositioned) {
  hasNonPositionedChildren = true;
  child.layout(nonPositionedConstraints, parentUsesSize: true);
  final Size childSize = child.size;
  width = math.max(width, childSize.width);
  height = math.max(height, childSize.height);
}
  child = childParentData.nextSibling;
}

   (4).根據是否包含positioned子節點,對stack進行尺寸調整。 

if (hasNonPositionedChildren) {
  size = new Size(width, height);
} else {
  size = constraints.biggest;
}

   (5).最后對子節點位置的調整,這個調整過程中,則根據alignment、positioned節點的絕對位置等信息,對子節點進行布局。
           第一步是根據positioned的絕對位置,計算出約束條件后進行布局。

if (childParentData.left != null && childParentData.right != null)
   childConstraints = childConstraints.tighten(width: size.width - childParentData.right - childParentData.left);
else if (childParentData.width != null)
   childConstraints = childConstraints.tighten(width: childParentData.width);

if (childParentData.top != null && childParentData.bottom != null)
   childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom - childParentData.top);
else if (childParentData.height != null)
   childConstraints = childConstraints.tighten(height: childParentData.height);

child.layout(childConstraints, parentUsesSize: true);

           第二步則是位置的調整,其中坐標的計算如下:

double x;
if (childParentData.left != null) {
  x = childParentData.left;
} else if (childParentData.right != null) {
  x = size.width - childParentData.right - child.size.width;
} else {
  x = _resolvedAlignment.alongOffset(size - child.size).dx;
}

if (x < 0.0 || x + child.size.width > size.width)
  _hasVisualOverflow = true;

double y;
if (childParentData.top != null) {
  y = childParentData.top;
} else if (childParentData.bottom != null) {
  y = size.height - childParentData.bottom - child.size.height;
} else {
  y = _resolvedAlignment.alongOffset(size - child.size).dy;
}

if (y < 0.0 || y + child.size.height > size.height)
 _hasVisualOverflow = true;

childParentData.offset = new Offset(x, y);
  • IndexedStack
    class MyIndexedStack extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Container(
          color: Colors.yellow,
          child: IndexedStack(
            index: 1,
            alignment: const Alignment(0.6, 0.6),
            children: <Widget>[
              new CircleAvatar(
                backgroundImage: AssetImage('1'),
                radius: 100.0,
              ),
              new Container(
                decoration: new BoxDecoration(
                  color: Colors.black45,
                ),
                child: new Text(
                  'Flutter--Demo',
                  style:new TextStyle(
                    fontSize: 20.0,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  )
                ),
              )
            ],
          ),
        );
      }
    }

    效果圖:


    源碼分析:

    其繪制代碼很簡單,因為繼承自Stack,布局方面表現基本一致,不同之處在於其繪制的時候,只是將第Index個child進行了繪制。

    @override
    void paintStack(PaintingContext context, Offset offset) {
     if (firstChild == null || index == null) return;
       final RenderBox child = _childAtIndex();
       final StackParentData childParentData = child.parentData;
       context.paintChild(child, childParentData.offset + offset);
    }
  • OverflowBox
    Container(
      color: Colors.green,
      width: 200.0,
      height: 200.0,
      padding: const EdgeInsets.all(5.0),
      child: OverflowBox(
        alignment: Alignment.topLeft,
        maxWidth: 300.0,
        maxHeight: 500.0,
        child: Container(
          color: Color(0x33FF00FF),
          width: 400.0,
          height: 400.0,
        ),
      ),
    )

    效果圖

    源碼解析:

    OverflowBox的源碼很簡單,我們先來看一下布局代碼:

    if (child != null) {
     child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
     alignChild();
    }

    如果child不為null,child則會按照計算出的constraints進行尺寸的調整,然后對齊。

    至於constraints的計算,則還是上面的邏輯,如果設置的有的話,就取這個值,如果沒有的話,就拿父節點的。

四,參考
  《Flutter學習之認知基礎組件
  《Flutter布局


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM