Flutter開發日記——Flutter佈局Widget詳解(下)

YYDev發表於2019-11-05

Row

1、簡介

  • Row元件是一個橫向排布的佈局元件,跟h5的Flex佈局一樣,只不過限定了橫向排布

2、建構函式

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:主軸方向的排布方式,由於是橫向佈局,其主軸是橫向的水平線
    • center:主軸中心
    • start:主軸起點
    • end:主軸末尾
    • spaceAround:主軸中間空白區域均分,首尾空白區域為1/2
    • spaceBetween:主軸中間空白區域均分,首尾沒有空白區域
    • spaceEvenly:主軸中間空白區域均分,首尾平分空白區域
  • mainAxisSize:主軸方向佔有空間的值
    • max:佔用最大空間
    • min:佔用最小空間
  • crossAxisAlignment:側軸方向的排布方式,由於是橫向佈局,其側軸是垂直的豎線
    • baseline:側軸與baseline對齊
    • center:側軸居中顯示
    • end:側軸末尾顯示
    • start:側軸起點顯示
    • stretch:側軸填滿顯示
  • textDirection:文字的排布方式
    • TextDirection.ltr:從左到右
    • TextDirection.rtl:從右到左
  • verticalDirection:子控制元件的排布方式
    • down:從左上角到右下角進行佈局
    • up:從右下角到左上角進行佈局
  • textBaseline:文字的基準線
  • children:子控制元件集合

3、例子

三個容器橫向排放

Widget _buildColumn() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      textDirection: TextDirection.ltr,
      textBaseline: TextBaseline.alphabetic,
      mainAxisSize: MainAxisSize.max,
      verticalDirection: VerticalDirection.down,
      children: <Widget>[
        Container(
          height: 50,
          width: 50,
          color: Colors.blueAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.greenAccent,
        )
      ],
    );
}
複製程式碼

在這裡插入圖片描述

Column

1、簡介

  • Column元件是一個豎向排布的佈局元件,跟h5的Flex佈局一樣,只不過限定了豎向排布

2、建構函式

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>[],
})
複製程式碼
  • mainAxisAlignment:主軸方向的排布方式,由於是豎向佈局,其主軸是豎向的垂直線
    • center:主軸中心
    • start:主軸起點
    • end:主軸末尾
    • spaceAround:主軸中間空白區域均分,首尾空白區域為1/2
    • spaceBetween:主軸中間空白區域均分,首尾沒有空白區域
    • spaceEvenly:主軸中間空白區域均分,首尾平分空白區域
  • mainAxisSize:主軸方向佔有空間的值
    • max:佔用最大空間
    • min:佔用最小空間
  • crossAxisAlignment:側軸方向的排布方式,由於是豎向佈局,其側軸是橫向的水平線
    • baseline:側軸與baseline對齊
    • center:側軸居中顯示
    • end:側軸末尾顯示
    • start:側軸起點顯示
    • stretch:側軸填滿顯示
  • textDirection:文字的排布方式
    • TextDirection.ltr:從左到右
    • TextDirection.rtl:從右到左
  • verticalDirection:子控制元件的排布方式
    • down:從左上角到右下角進行佈局
    • up:從右下角到左上角進行佈局
  • textBaseline:文字的基準線
  • children:子控制元件集合

3、例子

三個容器豎向排放,由於設定了up的擺放方式,導致位置是倒過來的

Widget _buildColumn() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      verticalDirection: VerticalDirection.up,
      textBaseline: TextBaseline.alphabetic,
      textDirection: TextDirection.ltr,
      children: <Widget>[Container(
        height: 50,
        width: 50,
        color: Colors.blueAccent,
      ),
      Container(
        height: 50,
        width: 50,
        color: Colors.redAccent,
      ),
      Container(
        height: 50,
        width: 50,
        color: Colors.greenAccent,
      )],
    );
}
複製程式碼

在這裡插入圖片描述

Stack

1、簡介

  • Stack元件是可以互相疊在一起的佈局,類似於一個棧
  • Stack元件通過alignment去決定子控制元件的位置

2、建構函式

Stack({
  Key key,
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
複製程式碼
  • alignment:控制child的對齊方式
  • textDirection:文字的排布方式
    • TextDirection.ltr:從左到右
    • TextDirection.rtl:從右到左
  • fit:定義子控制元件集合的尺寸
    • StackFit.loose:子控制元件的尺寸不受限制
    • StackFit.expand:子控制元件的尺寸儘可能大
    • StackFit.passthrough:不改變子控制元件的約束條件
  • overflow:超出佈局本身的處理
    • Overflow.clip:超出的佈局被裁剪
    • Overflow.visible:超出的佈局被顯示
  • children:子控制元件集合

3、例子

將兩個容器疊加在一起,並且在對齊右下角

Widget _buildColumn() {
    return Stack(
      textDirection: TextDirection.ltr,
      alignment: Alignment.bottomRight,
      overflow: Overflow.visible,
      fit: StackFit.loose,
      children: <Widget>[
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        )
      ],
    );
}
複製程式碼

在這裡插入圖片描述

IndexedStack

1、簡介

  • IndexedStack控制元件本質繼承了Stack元件,區別在於通過index增加了層級控制,控制層級的顯示

2、建構函式

IndexedStack({
    Key key,
    AlignmentGeometry alignment = AlignmentDirectional.topStart,
    TextDirection textDirection,
    StackFit sizing = StackFit.loose,
    this.index = 0,
    List<Widget> children = const <Widget>[],
}) 
複製程式碼
  • alignment:控制child的對齊方式
  • textDirection:文字的排布方式
    • TextDirection.ltr:從左到右
    • TextDirection.rtl:從右到左
  • sizing:定義子控制元件集合的尺寸
    • StackFit.loose:子控制元件的尺寸不受限制
    • StackFit.expand:子控制元件的尺寸儘可能大
    • StackFit.passthrough:不改變子控制元件的約束條件
  • index:控制第幾個child的顯示,0表示第一個
  • children:子控制元件集合

3、例子

通過index控制第二個容器的出現,第一個容器則隱藏

Widget _buildColumn() {
    return IndexedStack(
      index: 1,
      sizing: StackFit.loose,
      textDirection: TextDirection.ltr,
      alignment: Alignment.bottomRight,
      children: <Widget>[
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        )
      ],
    );
}
複製程式碼

在這裡插入圖片描述

Flow

1、簡介

  • Flow控制元件相當於流式佈局,其佈局的擺佈方式任由開發者去控制

2、建構函式

Flow({
    Key key,
    @required this.delegate,
    List<Widget> children = const <Widget>[],
})
複製程式碼
  • delegate:佈局管理器,可以管理佈局的擺放
  • children:子控制元件集合

3、例子

在Flow佈局中擺放一堆容器,並且大小不一

Widget _buildColumn() {
    return Flow(
      delegate: GridFlowDelegate(margin: EdgeInsets.all(10.0)),
      children: <Widget>[
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        ),
        Container(
          height: 50,
          width: 50,
          color: Colors.redAccent,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.greenAccent,
        )
      ],
    );
}
複製程式碼

在佈局管理器中,自定義我們的擺佈方式,以一行中最高的容器作為換行的高度進行橫向擺佈

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

  GridFlowDelegate({this.margin});

  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left; //繪製子控制元件的x座標
    var y = margin.top; //繪製子控制元件的y座標
    var maxHeightIndex  = 0; //同一行中最大高度的子控制元件的索引,用於換行
    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: Matrix4.translationValues(x, y, 0.0));
        // 下一次的x座標
        x = w + margin.left;
        // 在第二個控制元件開始取同一行的最大高度的控制元件
        if (i >= 1){
          var currentHeight = context.getChildSize(i).height + margin.top + margin.bottom;
          var lastHeight = context.getChildSize(maxHeightIndex).height + margin.top + margin.bottom;
          if (currentHeight > lastHeight) {
            // 保留最大高度的索引值
            maxHeightIndex = i;
          }
        }
      }else{
        // 如果超出當前未分配的寬度,則先歸位x座標恢復預設值,從左邊開始重新分配空間
        x = margin.left;
        // y座標
        y += context.getChildSize(maxHeightIndex).height + margin.top + margin.bottom;
        // 找到座標後直接平移到對應位置畫出來
        context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));
        // 下一次的x座標需要將它加上自己的寬度,和自己的左右邊距
        x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

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

在這裡插入圖片描述

Table

1、簡介

  • Table控制元件具有控制寬度和位置的表格控制元件

2、建構函式

Table({
  Key key,
  this.children = const <TableRow>[],
  this.columnWidths,
  this.defaultColumnWidth = const FlexColumnWidth(1.0),
  this.textDirection,
  this.border,
  this.defaultVerticalAlignment = TableCellVerticalAlignment.top,
  this.textBaseline,
})
複製程式碼
  • children:子控制元件集合
  • columnWidths:表格的寬度
  • defaultColumnWidth:預設表格的寬度
    • top:頂部
    • middle:垂直居中
    • bottom:底部
    • baseline:文字baseline對齊
    • fill:充滿整個單元
  • textDirection:文字的對齊方式
  • border:表格的邊框
  • defaultVerticalAlignment:預設的表格位置對齊方式
  • textBaseline:文字baseline型別
    • alphabetic:對齊字元底部的水平線
    • ideographic:對齊表意字元的水平線

3、例子

將文字居底對齊的表格

Widget _buildColumn() {
    return Table(
        textDirection: TextDirection.ltr,
        textBaseline: TextBaseline.alphabetic,
        defaultColumnWidth: FixedColumnWidth(80.0),
        defaultVerticalAlignment: TableCellVerticalAlignment.bottom,
        border:
            TableBorder.all(color: Colors.blueGrey, style: BorderStyle.solid),
        columnWidths: {
          0: FixedColumnWidth(50.0),
          1: FixedColumnWidth(100.0),
          2: FixedColumnWidth(100.0),
        },
        children: <TableRow>[
          TableRow(children: [
            Text("序號"),
            Text("名字"),
            Text("成績"),
          ]),
          TableRow(children: [
            Text("1"),
            Text("張三"),
            Text("80"),
          ]),
          TableRow(children: [
            Text("2"),
            Text("李四"),
            Text("88"),
          ]),
          TableRow(children: [
            Text("3"),
            Text("王五"),
            Text("92"),
          ])
        ]);
}
複製程式碼

在這裡插入圖片描述

Wrap

1、簡介

  • Wrap控制元件相當於流式佈局,會自動換行,比Flex自定義的功能好用

2、建構函式

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>[],
})
複製程式碼
  • direction:主軸的方向
    • Axis.horizontal:橫向排布
    • Axis.vertical:豎向排布
  • alignment:主軸方向上的對齊方式
  • spacing:主軸方向上的間距
  • runAlignment:橫向排布的話,表示一行的對齊方式,豎向排布的話,表示一列的對齊方式
  • runSpacing:一行或者一列方向上的間距
  • crossAxisAlignment:側軸方向的對齊方式
  • textDirection:文字的對齊方式
  • verticalDirection:子控制元件的排布方式
    • down:從左上角到右下角進行佈局
    • up:從右下角到左上角進行佈局
  • children:子控制元件集合

3、例子

通過Wrap控制元件建立出豎向的流式佈局

Widget _buildColumn() {
    return Wrap(
      textDirection: TextDirection.ltr,
      alignment: WrapAlignment.center,
      verticalDirection: VerticalDirection.down,
      crossAxisAlignment: WrapCrossAlignment.center,
      direction: Axis.horizontal,
      runAlignment: WrapAlignment.center,
      runSpacing: 10.0,
      spacing: 10.0,
      children: <Widget>[
        Chip(
          label: Text("張三張三張三"),
        ),
        Chip(
          label: Text("李四李四李四"),
        ),
        Chip(
          label: Text("王五王五王五"),
        ),
        Chip(
          label: Text("趙六趙六趙六"),
        ),
        Chip(
          label: Text("錢七"),
        ),
        Chip(
          label: Text("孫八"),
        ),
      ],
    );
}
複製程式碼

在這裡插入圖片描述

ListBody

1、簡介

  • ListBody控制元件一般會配合ListView使用,屬於ListView的子節點
  • ListBody控制元件的作用是按給定的軸方向,按照順序排列子節點

2、建構函式

ListBody({
  Key key,
  this.mainAxis = Axis.vertical,
  this.reverse = false,
  List<Widget> children = const <Widget>[],
})
複製程式碼
  • mainAxis:主軸方向
    • Axis.vertical:垂直方向
    • Axis.horizontal:橫向方向
  • reverse:是否控制元件翻轉過來
  • children:子控制元件集合

3、例子

顯示3個不同顏色的容器,由於其父容器是Flex,會導致ListBody的側軸被拉昇到最大

Widget _buildColumn() {
    return Flex(
      direction: Axis.vertical,
      children: <Widget>[
        ListBody(
          mainAxis: Axis.vertical,
          reverse: false,
          children: <Widget>[
            Container(
              height: 50,
              width: 50,
              color: Colors.blueAccent,
            ),
            Container(
              height: 50,
              width: 50,
              color: Colors.redAccent,
            ),
            Container(
              height: 50,
              width: 50,
              color: Colors.greenAccent,
            )
          ],
        ),
      ],
    );
}
複製程式碼

在這裡插入圖片描述

ListView

1、簡介

  • ListView控制元件是個功能豐富的列表元件

2、建構函式

ListView({
  Key key,
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  this.itemExtent,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double cacheExtent,
  List<Widget> children = const <Widget>[],
})
複製程式碼
  • scrollDirection:滾動放向
  • reverse:子控制元件是否翻轉
  • controller:用來控制滾動位置及監聽滾動事件
  • primary:當內容不足以滾動時,是否支援滾動
  • physics:控制使用者滾動檢視的互動
    • AlwaysScrollableScrollPhysics:列表總是可滾動的
    • PageScrollPhysics:一頁一頁的列表滑動,一般用於PageView控制元件用的滑動效果,滑動到末尾會有比較大的彈起
    • ClampingScrollPhysics:滾動時沒有回彈效果
    • NeverScrollableScrollPhysics:就算內容超過列表範圍也不會滑動
    • BouncingScrollPhysics:不論什麼平臺都會有回彈效果
    • FixedExtentScrollPhysics:僅滾動到子項而不存在任何偏移,必須與使用FixedExtentScrollController的可滾動物件一起使用
  • shrinkWrap:是否根據子widget的總長度來設定ListView的長度
  • padding:父控制元件的內邊距
  • itemExtent:指定子控制元件的固定高度
  • addAutomaticKeepAlives:是否將子控制元件包裹在AutomaticKeepAlive控制元件內
  • addRepaintBoundaries:是否將子控制元件包裹在 RepaintBoundary 控制元件內。用於避免列表滾動時的重繪
  • cacheExtent:可見區域的前後會有一定高度的空間去快取子控制元件,當滑動時就可以迅速呈現
  • children:子控制元件集合

3、例子

建立三個具有回彈功能的列表控制元件

Widget _buildColumn() {
    return ListView(
      physics: BouncingScrollPhysics(),
      cacheExtent: 10.0,
      primary: true,
      padding: EdgeInsets.all(15.0),
      reverse: false,
      scrollDirection: Axis.vertical,
      children: <Widget>[
        Container(
          height: 300,
          width: 300,
          color: Colors.blueAccent,
        ),
        Container(
          height: 300,
          width: 300,
          color: Colors.redAccent,
        ),
        Container(
          height: 300,
          width: 300,
          color: Colors.greenAccent,
        )
      ],
    );
}
複製程式碼

在這裡插入圖片描述

CustomMultiChildLayout

1、簡介

  • CustomMultiChildLayout控制元件和CustomSingleChildLayout類似,區別於CustomMultiChildLayout可以控制多個控制元件
  • CustomMultiChildLayout控制元件控制的子控制元件是通過Id進行區分和擺放

2、建構函式

CustomMultiChildLayout({
  Key key,
  @required this.delegate,
  List<Widget> children = const <Widget>[],
})
複製程式碼
  • delegate:控制子控制元件集合的代理類
  • children:子控制元件集合

3、例子

首先建立控制元件的代理類,包括控制元件的大小和控制元件的位置,通過id獲取傳遞過來的子控制元件,將description的控制元件放置在title下方

class IdLayoutDelegate extends MultiChildLayoutDelegate {
  IdLayoutDelegate();

  @override
  void performLayout(Size size) {
    BoxConstraints constraints = BoxConstraints(maxWidth: size.width);

    // 通過id獲取對應的控制元件大小
    Size titleSize = layoutChild("title", constraints);
    Size descriptionSize = layoutChild("description", constraints);

    // 擺放id的控制元件位置
    positionChild("title", Offset(0.0, 0.0));
    positionChild("description", Offset(0.0, titleSize.height));
  }

  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
    return oldDelegate != null;
  }
}
複製程式碼

通過id將對應的控制元件擺佈在對應的位置

Widget _buildColumn() {
    return CustomMultiChildLayout(
      delegate: IdLayoutDelegate(),
      children: <Widget>[
        LayoutId(
          id: "title",
          child: Text("Hensen"),
        ),
        LayoutId(
          id: "description",
          child: Text("Flutter工程師"),
        )
      ],
    );
}
複製程式碼

在這裡插入圖片描述

LayoutBuilder

1、簡介

  • LayoutBuilder控制元件可以通過獲取父控制元件的約束條件,從而控制佈局的返回結果

2、建構函式

const LayoutBuilder({
    Key key,
    LayoutWidgetBuilder builder,
})
複製程式碼
  • builder:子控制元件的構建者

3、例子

通過判斷父控制元件的尺寸大小,如果是大尺寸,就用大圖示,如果是小尺寸,就用小圖示

Widget _buildColumn() {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        if (constraints.maxWidth > 200.0) {
          // 尺寸大的
          return FlutterLogo(size: 200);
        }
        // 尺寸小的
        return FlutterLogo(size: 50);
      },
    );
}
複製程式碼

在這裡插入圖片描述

作者

Flutter開發日記——Flutter佈局Widget詳解(下)
Hensen_

相關文章