Flutter 多子 Widget 佈局之Flex、Expanded、Wrap、Flow


上文 Flutter 多子 Widget 佈局之線性佈局 Row、Column,介紹了Row、Column這兩個組件,查看源碼發現他倆都是繼承了Flex widget,這篇來學習學習彈性佈局Flex,Explanded

彈性佈局 Flex、Expanded

Flex 表示彈性,Expanded 表示擴張,在這裏將二者歸爲彈性佈局。

Flex

Flex顧名思義是彈性佈局,一般我們會使用它的子類Row 和 Column.

 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>[],
  }) 

Expanded

顧名思義是具有 擴展功能的widget ,查看源碼發現Expanded繼承自Flexible

構造函數也很簡單:

 const Expanded({
    Key key,
    int flex = 1,// 如果爲空或爲零, child 不擴張,如果爲非 0 則 child 在主軸上分配的空間爲 flex 指定的值
    @required Widget child,
  }) 

看個示例效果圖就一目瞭然了。
flex:1紅色部分和flex:2黃色部分將主軸方向的屏幕等分爲3分,紅色佔1/3,黃色佔2/3
在這裏插入圖片描述
示例代碼如下:

Column(
  children: <Widget>[
    Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: Container(
            height: 50.0,
            color: Colors.red,
            alignment: Alignment.center,
            child: Text('Expanded flex: 1',style: TextStyle(color: Colors.white),),
          )
        ),
        Expanded(
            flex: 2,
            child: Container(
              height: 50.0,
              color: Colors.yellow,
              alignment: Alignment.center,
              child: Text('Expanded flex: 2',style: TextStyle(color: Colors.green),),
            )
        ),
      ],
    ),

  ],
)

流式佈局 Wrap、Flow

Wrap : 流式佈局,默認是水平方向擺列 child,當頁面顯示不下時會讓子控件自動換行。
Flow:流式佈局,能根據座標繪製child,定製性強也相對複雜一些,能實現Wrap控件相同的效果

由於Flow內部做了是否重繪的處理,所以效率上高一些。
但是使用上相對Wrap複雜一些,所以使用時能用Wrap實現的儘量使用Wrap吧。

Wrap

相關字段作用如下:

Wrap({
    Key key,
    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>[],//子控件集合
  })

可以看到以上參數基本和Row,Column相似,僅多了兩個字段:

this.spacing = 0.0,//主軸上子控件中間的間距
this.runSpacing = 0.0,// 交叉軸上子控件之間的間距

我們通過示例效果來理解這兩個字段:
在這裏插入圖片描述
示例僞代碼:

/// 子佈局集合
List<Widget> _wrapItems() => List.generate(8, (index){
    return Container(
      color: Colors.red,
      width: 50,
      height: 50,
      alignment: Alignment.center,
      child: Text('$index',style: TextStyle(color: Colors.white),),
    );
  }
);

@override
Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(
        title: Text('Wrap'),
      ),
      body: Wrap(
        spacing: 10,// 水平間距
        runSpacing: 5,// 垂直間距
        children: _wrapItems()
      )
  );
}

Flow

Flow詳解可以查看這兩篇文章:

查看源碼,發現Flow的字段也是很簡單:

Flow({
    Key key,
    @required this.delegate,
    List<Widget> children = const <Widget>[],
  })

Flow的核心是delegate,而delegate裏面可以根據信息來計算child的擺放位置。

把上面的Wrap實現的效果,用Flow控件實現,
在這裏插入圖片描述
直接放源碼,註釋也在源碼中說明了。


class FlowTestPage extends StatefulWidget {
  @override
  _FlowTestPageState createState() {
    return _FlowTestPageState();
  }
}

class _FlowTestPageState extends State<FlowTestPage> {
  
  List<Widget> _wrapItems() => List.generate(8, (index){
      return Container(
        color: Colors.red,
        width: 50,
        height: 50,
        alignment: Alignment.center,
        child: Text('$index',style: TextStyle(color: Colors.white),),
      );
    }
  );


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Flow'),
        ),
        body: Flow(
            delegate: TestFlowDelegate(margin: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0)),
            children: _wrapItems()
        )
    );
  }
}

class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin = EdgeInsets.zero;

  TestFlowDelegate({this.margin});

  @override
  void paintChildren(FlowPaintingContext context) {
    /// 左邊距
    var x = margin.left;
    var y = margin.top;

    for (int i = 0; i < context.childCount; i++) {
      /// child 的寬度(沒有加上margin left)
      var w = context.getChildSize(i).width + x + margin.right;
      /// child 的寬度 < 屏幕的寬度, 單行繪製
      if (w < context.size.width) {
        context.paintChild(i, transform: new Matrix4.translationValues(x, y, 0.0));
        /// 當前繪製的 child 距離左邊距大小(加上margin left)
        x = w + margin.left;

      } else {
        /// 恢復繪製的 child 左邊距
        x = margin.left;
        /// 行數 + 1
        y += context.getChildSize(i).height + margin.top + margin.bottom;
        context.paintChild(i, transform: new Matrix4.translationValues(x, y, 0.0));
        /// 下一行開始繪製 child 的左邊距
        x += context.getChildSize(i).width + x + margin.right;
      }
    }
  }

  /// 是否重繪 FlowDelegate
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

完~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章