初略講解Flutter的Wrap和Flow(流式佈局)

zane發表於2019-08-19

在介紹RowColumn時,如果子元件超出螢幕範圍,則會報溢位錯誤,如:

Row(
  children: <Widget>[
    Text("xxx"*100)
  ],
);
複製程式碼

執行效果如下:

初略講解Flutter的Wrap和Flow(流式佈局)

可以看到,右邊溢位部分報錯,這是因為Row預設只有一行,如果超出螢幕不會折行,我們把超出螢幕顯示範圍會自動折行的佈局稱為流式佈局。Flutter中通過WrapFlow來支援流式佈局,將上例中的Row換成Wrap後溢位部分則會自動折行,下面我們分別介紹一下WrapFlow

Wrap

以下是Wrap的定義:

Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,
  this.runAlignment = WrapAlignment.start,
  this.runSpacing = 0.0,
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})
複製程式碼

我們可以看到Wrap的很多屬性在RowColumn以及Flex中都有,如directioncrossAxisAlignmenttextDirectionverticalDirectionchildren等,這些屬性的意義是相同的,不過需要注意的是,Wrap中的crossAxisAlignment屬性和RowColumn以及Flex中的crossAxisAlignment略有差異:

  • 取值方式不同;Wrap中的crossAxisAlignment取值為WrapCrossAlignment.start,而RowColumn以及Flex中的crossAxisAlignment則取值為CrossAxisAlignment.center,前者使用WrapCrossAlignment來取值,而後者使用CrossAxisAlignment來取值。

  • 在主軸為垂直方向時,子元件對齊方式略有差異;如在WrapcrossAxisAlignment值為WrapCrossAlignment.center,且directionAxis.vertical,同時寬度為螢幕寬度,此時Wrap中的所有子元件都將以最大元件的寬度居中對齊,而Column中的crossAxisAlignment值為CrossAxisAlignment.center,且寬度為螢幕寬度,那麼它們當中的所有子元件都將在螢幕中間且以最大元件寬度居中對齊。

    Wrap的示例程式碼:

    ConstrainedBox(
      constraints: BoxConstraints(minWidth: double.infinity),
      child: Container(
        color: Colors.blue,
        child: Wrap(
          direction: Axis.vertical,
          crossAxisAlignment: WrapCrossAlignment.center,
          children: <Widget>[
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
              label: Text("Hamilton"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
              label: Text("LafayetteLafayette"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
              label: Text("Mulligan"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
              label: Text("Laurens"),
            ),
          ],
        ),
      ),
    ),
    複製程式碼

    執行效果:

    初略講解Flutter的Wrap和Flow(流式佈局)

    Column的示例程式碼:

    ConstrainedBox(
      constraints: BoxConstraints(minWidth: double.infinity),
      child: Container(
        color: Colors.blue,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
              label: Text("Hamilton"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
              label: Text("LafayetteLafayette"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
              label: Text("Mulligan"),
            ),
            Chip(
              avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
              label: Text("Laurens"),
            ),
          ],
        ),
      ),
    ),
    複製程式碼

    執行效果:

    初略講解Flutter的Wrap和Flow(流式佈局)

除此之外,其它相同屬性並無差異,在此就不再一一贅述,讀者可以查閱前面介紹的RowColum的文章。接下來我們看一下Wrap特有的幾個屬性:

  • spacing:主軸方向子元件的間距
  • runSpacing:縱軸方向子元件的間距
  • alignment:主軸方向子元件的對齊方式
  • runAlignment:縱軸方向子元件的對齊方式

示例

Container(
  color: Colors.blue,
  child: Wrap(
    spacing: 20.0,
    runSpacing: 10.0,
    alignment: WrapAlignment.center,
    children: <Widget>[
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
        label: Text("Hamilton"),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
        label: Text("Lafayette"),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
        label: Text("Mulligan"),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
        label: Text("Laurens"),
      ),
      Chip(
        avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
        label: Text("Zane"),
      ),
    ],
  ),
),
複製程式碼

執行效果如下:

初略講解Flutter的Wrap和Flow(流式佈局)

我們可以看出,Wrap的寬度和高度都是自適應的,取決於它所包含的子元件,如果想要設定Wrap的寬度和高度,可以通過ConstrainedBoxSizedBox來強制更改寬度和高度限制,如:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: double.infinity, minHeight: 600.0),
  child: Container(
    color: Colors.blue,
    child: Wrap(
      alignment: WrapAlignment.center,
      runAlignment: WrapAlignment.center,
      children: <Widget>[
        Chip(
          avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("A"),),
          label: Text("Hamilton"),
        ),
        Chip(
          avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("B"),),
          label: Text("LafayetteLafayette"),
        ),
        Chip(
          avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("C"),),
          label: Text("Mulligan"),
        ),
        Chip(
          avatar: CircleAvatar(backgroundColor: Colors.blue, child: Text("D"),),
          label: Text("Laurens"),
        ),
      ],
    ),
  ),
),
複製程式碼

Flow

我們一般很少會使用Flow,因為其過於複雜,需要自己實現子元件的位置轉換,所以在很多場景下首先要考慮的是Wrap是否滿足需求。Flow主要用於一些需要自定義佈局策略或效能要求較高(如動畫中)的場景。

Flow的優點

  • 效能好:Flow是一個對子元件尺寸以及位置調整非常高效的控制元件,Flow用轉換矩陣在對子元件進行位置調整的時候進行了優化:在Flow定位過後,如果子元件的尺寸或者位置發生了變化,在FlowDelegate中的paintChildren()方法中呼叫context.paintChild進行重繪,而context.paintChild在重繪時使用了轉換矩陣,並沒有實際調整元件位置。
  • 靈活:由於我們需要自己實現FlowDelegate中的paintChildren()方法,所以需要計算每一個元件的位置,因此可以自定義佈局策略。

Flow的缺點

  • 使用複雜(程式碼實現還是要有點功底的)。
  • 不能自適應子元件大小,必須通過指定父容器大小或實現TestFlowDelegategetSize返回固定大小。

示例

自定義流式佈局:

Flow(
  delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
  children: <Widget>[
    new Container(width: 80.0, height:80.0, color: Colors.red,),
    new Container(width: 80.0, height:80.0, color: Colors.green,),
    new Container(width: 80.0, height:80.0, color: Colors.blue,),
    new Container(width: 80.0, height:80.0,  color: Colors.yellow,),
    new Container(width: 80.0, height:80.0, color: Colors.brown,),
    new Container(width: 80.0, height:80.0,  color: Colors.purple,),
  ],
)
複製程式碼

實現TestFlowDelegate

class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin = EdgeInsets.zero;
  TestFlowDelegate({this.margin});
  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    //計算每一個子widget的位置  
    for (int i = 0; i < context.childCount; i++) {
      var w = context.getChildSize(i).width + x + margin.right;
      if (w < context.size.width) {
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
        x = w + margin.left;
      } else {
        x = margin.left;
        y += context.getChildSize(i).height + margin.top + margin.bottom;
        //繪製子widget(有優化)  
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
         x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

  @override
  getSize(BoxConstraints constraints){
    //指定Flow的大小  
    return Size(double.infinity,200.0);
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}
複製程式碼

執行效果:

初略講解Flutter的Wrap和Flow(流式佈局)

可以看到我們主要的任務就是實現paintChildren()方法,該方法的主要作用是確定每個子元件的位置。由於Flow不能自適應子元件的大小,因此我們通過在getSize()返回中返回一個固定大小來指定Flow的大小。

相關文章