Flutter 布局(九)- Flow筷笨、Table憔鬼、Wrap詳解

本文主要介紹Flutter布局中的Flow龟劲、Table、Wrap控件轴或,詳細(xì)介紹了其布局行為以及使用場(chǎng)景昌跌,并對(duì)源碼進(jìn)行了分析。

1. Flow

A widget that implements the flow layout algorithm.

1.1 簡(jiǎn)介

Flow按照解釋的那樣照雁,是一個(gè)實(shí)現(xiàn)流式布局算法的控件蚕愤。流式布局在大前端是很常見的布局方式,但是一般使用Flow很少饺蚊,因?yàn)槠溥^于復(fù)雜缅阳,很多場(chǎng)景下都會(huì)去使用Wrap啦撮。

1.2 布局行為

Flow官方介紹是一個(gè)對(duì)child尺寸以及位置調(diào)整非常高效的控件蒜绽,主要是得益于其FlowDelegate舟肉。另外Flow在用轉(zhuǎn)換矩陣(transformation matrices)對(duì)child進(jìn)行位置調(diào)整的時(shí)候進(jìn)行了優(yōu)化。

Flow以及其child的一些約束都會(huì)受到FlowDelegate的控制燕酷,例如重寫FlowDelegate中的geiSize籍凝,可以設(shè)置Flow的尺寸,重寫其getConstraintsForChild方法苗缩,可以設(shè)置每個(gè)child的布局約束條件饵蒂。

Flow之所以高效,是因?yàn)槠湓诙ㄎ贿^后酱讶,如果使用FlowDelegate中的paintChildren改變child的尺寸或者位置退盯,只是重繪,并沒有實(shí)際調(diào)整其位置泻肯。

1.3 繼承關(guān)系

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flow

1.4 示例代碼

const width = 80.0;
const height = 60.0;

Flow(
  delegate: TestFlowDelegate(margin: EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0)),
  children: <Widget>[
    new Container(width: width, height: height, color: Colors.yellow,),
    new Container(width: width, height: height, color: Colors.green,),
    new Container(width: width, height: height, color: Colors.red,),
    new Container(width: width, height: height, color: Colors.black,),
    new Container(width: width, height: height, color: Colors.blue,),
    new Container(width: width, height: height, color: Colors.lightGreenAccent,),
  ],
)

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++) {
      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;
        context.paintChild(i,
            transform: new Matrix4.translationValues(
                x, y, 0.0));
        x += context.getChildSize(i).width + margin.left + margin.right;
      }
    }
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
}

樣例其實(shí)并不復(fù)雜渊迁,F(xiàn)lowDelegate需要自己實(shí)現(xiàn)child的繪制,其實(shí)大多數(shù)時(shí)候就是位置的擺放软免。上面例子中宫纬,對(duì)每個(gè)child按照給定的margin值焚挠,進(jìn)行排列膏萧,如果超出一行,則在下一行進(jìn)行布局蝌衔。

Flow樣例

另外榛泛,對(duì)這個(gè)例子多做一個(gè)說(shuō)明,對(duì)于上述child寬度的變化噩斟,這個(gè)例子是沒問題的曹锨,如果每個(gè)child的高度不同,則需要對(duì)代碼進(jìn)行調(diào)整剃允,具體的調(diào)整是換行的時(shí)候沛简,需要根據(jù)上一行的最大高度來(lái)確定下一行的起始y坐標(biāo)齐鲤。

1.5 源碼解析

構(gòu)造函數(shù)如下:

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

1.5.1 屬性解析

delegate:影響Flow具體布局的FlowDelegate。

其中FlowDelegate包含如下幾個(gè)方法:

  • getConstraintsForChild: 設(shè)置每個(gè)child的布局約束條件椒楣,會(huì)覆蓋已有的给郊;
  • getSize:設(shè)置Flow的尺寸;
  • paintChildren:child的繪制控制代碼捧灰,可以調(diào)整尺寸位置淆九,寫起來(lái)比較的繁瑣;
  • shouldRepaint:是否需要重繪毛俏;
  • shouldRelayout:是否需要重新布局炭庙。

其中,我們平時(shí)使用的時(shí)候煌寇,一般會(huì)使用到paintChildren以及shouldRepaint兩個(gè)方法焕蹄。

1.5.2 源碼

我們先來(lái)看一下Flow的布局代碼

Size _getSize(BoxConstraints constraints) {
  assert(constraints.debugAssertIsValid());
  return constraints.constrain(_delegate.getSize(constraints));
}

@override
void performLayout() {
  size = _getSize(constraints);
  int i = 0;
  _randomAccessChildren.clear();
  RenderBox child = firstChild;
  while (child != null) {
    _randomAccessChildren.add(child);
    final BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);
    child.layout(innerConstraints, parentUsesSize: true);
    final FlowParentData childParentData = child.parentData;
    childParentData.offset = Offset.zero;
    child = childParentData.nextSibling;
    i += 1;
  }
}

可以看到Flow尺寸的取值,直接來(lái)自于delegate的getSize方法阀溶。對(duì)于每一個(gè)child擦盾,則是將delegate中的getConstraintsForChild設(shè)置的約束條件,設(shè)置在child上淌哟。

Flow布局上的表現(xiàn)迹卢,受Delegate中g(shù)etSize以及getConstraintsForChild兩個(gè)方法的影響。第一個(gè)方法設(shè)置其尺寸徒仓,第二個(gè)方法設(shè)置其children的布局約束條件腐碱。

接下來(lái)我們來(lái)看一下其繪制方法。

void _paintWithDelegate(PaintingContext context, Offset offset) {
  _lastPaintOrder.clear();
  _paintingContext = context;
  _paintingOffset = offset;
  for (RenderBox child in _randomAccessChildren) {
    final FlowParentData childParentData = child.parentData;
    childParentData._transform = null;
  }
  try {
    _delegate.paintChildren(this);
  } finally {
    _paintingContext = null;
    _paintingOffset = null;
  }
}

它的繪制方法非常的簡(jiǎn)單掉弛,先將上次設(shè)置的參數(shù)都初始化症见,然后調(diào)用delegate中的paintChildren進(jìn)行繪制。在paintChildren中會(huì)調(diào)用paintChild方法去繪制每個(gè)child殃饿,我們接下來(lái)看下其代碼谋作。

@override
  void paintChild(int i, { Matrix4 transform, double opacity = 1.0 }) {
    transform ??= new Matrix4.identity();
    final RenderBox child = _randomAccessChildren[i];
    final FlowParentData childParentData = child.parentData;
    _lastPaintOrder.add(i);
    childParentData._transform = transform;
    
    if (opacity == 0.0)
      return;

    void painter(PaintingContext context, Offset offset) {
      context.paintChild(child, offset);
    }
    
    if (opacity == 1.0) {
      _paintingContext.pushTransform(needsCompositing, _paintingOffset, transform, painter);
    } else {
      _paintingContext.pushOpacity(_paintingOffset, _getAlphaFromOpacity(opacity), (PaintingContext context, Offset offset) {
        context.pushTransform(needsCompositing, offset, transform, painter);
      });
    }
  }

paitChild函數(shù)首先會(huì)將transform值設(shè)在child上,然后根據(jù)opacity值乎芳,決定其繪制的表現(xiàn)遵蚜。

  • 當(dāng)opacity為0時(shí),只是設(shè)置了transform值奈惑,這樣做是為了讓其響應(yīng)區(qū)域跟隨調(diào)整吭净,雖然不顯示出來(lái);
  • 當(dāng)opacity為1的時(shí)候肴甸,只是進(jìn)行Transform操作寂殉;
  • 當(dāng)opacity大于0小于1時(shí),先調(diào)整其透明度原在,再進(jìn)行Transform操作友扰。

至于其為什么高效彤叉,主要是因?yàn)樗牟季趾瘮?shù)不牽涉到child的布局,而在繪制的時(shí)候村怪,則根據(jù)delegate中的策略姆坚,進(jìn)行有效的繪制。

1.6 使用場(chǎng)景

Flow在一些定制化的流式布局中实愚,有可用場(chǎng)景兼呵,但是一般寫起來(lái)比較復(fù)雜,但勝在靈活性以及其高效腊敲。

2. Table

A widget that uses the table layout algorithm for its children.

2.1 簡(jiǎn)介

每一種移動(dòng)端布局中都會(huì)有一種table布局击喂,這種控件太常見了。至于其表現(xiàn)形式碰辅,完全可以借鑒其他移動(dòng)端的懂昂,通俗點(diǎn)講,就是表格没宾。

2.2 布局行為

表格的每一行的高度凌彬,由其內(nèi)容決定,每一列的寬度循衰,則由columnWidths屬性單獨(dú)控制铲敛。

2.3 繼承關(guān)系

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > Table

2.4 示例代碼

Table(
  columnWidths: const <int, TableColumnWidth>{
    0: FixedColumnWidth(50.0),
    1: FixedColumnWidth(100.0),
    2: FixedColumnWidth(50.0),
    3: FixedColumnWidth(100.0),
  },
  border: TableBorder.all(color: Colors.red, width: 1.0, style: BorderStyle.solid),
  children: const <TableRow>[
    TableRow(
      children: <Widget>[
        Text('A1'),
        Text('B1'),
        Text('C1'),
        Text('D1'),
      ],
    ),
    TableRow(
      children: <Widget>[
        Text('A2'),
        Text('B2'),
        Text('C2'),
        Text('D2'),
      ],
    ),
    TableRow(
      children: <Widget>[
        Text('A3'),
        Text('B3'),
        Text('C3'),
        Text('D3'),
      ],
    ),
  ],
)

一個(gè)三行四列的表格,第一三行寬度為50会钝,第二四行寬度為100伐蒋。

Table樣例

2.5 源碼解析

構(gòu)造函數(shù)如下:

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

2.5.1 屬性解析

columnWidths:設(shè)置每一列的寬度。

defaultColumnWidth:默認(rèn)的每一列寬度值迁酸,默認(rèn)情況下均分先鱼。

textDirection:文字方向,一般無(wú)需考慮奸鬓。

border:表格邊框焙畔。

defaultVerticalAlignment:每一個(gè)cell的垂直方向的alignment。

總共包含5種:

  • top:被放置在的頂部串远;
  • middle:垂直居中宏多;
  • bottom:放置在底部;
  • baseline:文本baseline對(duì)齊抑淫;
  • fill:充滿整個(gè)cell绷落。

textBaseline:defaultVerticalAlignment為baseline的時(shí)候,會(huì)用到這個(gè)屬性始苇。

2.5.2 源碼

我們直接來(lái)看其布局源碼:

第一步,當(dāng)行或者列為0的時(shí)候筐喳,將自身尺寸設(shè)為0x0催式。

if (rows * columns == 0) {
  size = constraints.constrain(const Size(0.0, 0.0));
  return;
}

第二步函喉,根據(jù)textDirection值,設(shè)置方向荣月,一般在阿拉伯語(yǔ)系中管呵,一些文本都是從右往左現(xiàn)實(shí)的,平時(shí)使用時(shí)哺窄,不需要去考慮這個(gè)屬性捐下。

switch (textDirection) {
  case TextDirection.rtl:
    positions[columns - 1] = 0.0;
    for (int x = columns - 2; x >= 0; x -= 1)
      positions[x] = positions[x+1] + widths[x+1];
    _columnLefts = positions.reversed;
    tableWidth = positions.first + widths.first;
    break;
  case TextDirection.ltr:
    positions[0] = 0.0;
    for (int x = 1; x < columns; x += 1)
      positions[x] = positions[x-1] + widths[x-1];
    _columnLefts = positions;
    tableWidth = positions.last + widths.last;
    break;
}

第三步,設(shè)置每一個(gè)cell的尺寸萌业。

  for (int x = 0; x < columns; x += 1) {
    final int xy = x + y * columns;
    final RenderBox child = _children[xy];
    if (child != null) {
      final TableCellParentData childParentData = child.parentData;
      childParentData.x = x;
      childParentData.y = y;
      switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
        case TableCellVerticalAlignment.baseline:
          child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
          final double childBaseline = child.getDistanceToBaseline(textBaseline, onlyReal: true);
          if (childBaseline != null) {
            beforeBaselineDistance = math.max(beforeBaselineDistance, childBaseline);
            afterBaselineDistance = math.max(afterBaselineDistance, child.size.height - childBaseline);
            baselines[x] = childBaseline;
            haveBaseline = true;
          } else {
            rowHeight = math.max(rowHeight, child.size.height);
            childParentData.offset = new Offset(positions[x], rowTop);
          }
          break;
        case TableCellVerticalAlignment.top:
        case TableCellVerticalAlignment.middle:
        case TableCellVerticalAlignment.bottom:
          child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
          rowHeight = math.max(rowHeight, child.size.height);
          break;
        case TableCellVerticalAlignment.fill:
          break;
      }
    }
  }

第四步坷襟,如果有baseline則進(jìn)行相關(guān)設(shè)置。

if (haveBaseline) {
  if (y == 0)
    _baselineDistance = beforeBaselineDistance;
    rowHeight = math.max(rowHeight, beforeBaselineDistance + afterBaselineDistance);
}

第五步生年,根據(jù)alignment婴程,調(diào)整child的位置。

  for (int x = 0; x < columns; x += 1) {
    final int xy = x + y * columns;
    final RenderBox child = _children[xy];
    if (child != null) {
      final TableCellParentData childParentData = child.parentData;
      switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
        case TableCellVerticalAlignment.baseline:
          if (baselines[x] != null)
            childParentData.offset = new Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x]);
          break;
        case TableCellVerticalAlignment.top:
          childParentData.offset = new Offset(positions[x], rowTop);
          break;
        case TableCellVerticalAlignment.middle:
          childParentData.offset = new Offset(positions[x], rowTop + (rowHeight - child.size.height) / 2.0);
          break;
        case TableCellVerticalAlignment.bottom:
          childParentData.offset = new Offset(positions[x], rowTop + rowHeight - child.size.height);
          break;
        case TableCellVerticalAlignment.fill:
          child.layout(new BoxConstraints.tightFor(width: widths[x], height: rowHeight));
          childParentData.offset = new Offset(positions[x], rowTop);
          break;
      }
    }
  }

最后一步抱婉,則是根據(jù)每一行的寬度以及每一列的高度档叔,設(shè)置Table的尺寸。

size = constraints.constrain(new Size(tableWidth, rowTop));

最后梳理一下整個(gè)的布局流程:

  • 當(dāng)行或者列為0的時(shí)候蒸绩,將自身尺寸設(shè)為0x0衙四;
  • 根據(jù)textDirection進(jìn)行相關(guān)設(shè)置;
  • 設(shè)置cell的尺寸患亿;
  • 如果設(shè)置了baseline届搁,則進(jìn)行相關(guān)設(shè)置;
  • 根據(jù)alignment設(shè)置cell垂直方向的位置窍育;
  • 設(shè)置Table的尺寸卡睦。

如果經(jīng)常關(guān)注系列文章的讀者,可能會(huì)發(fā)現(xiàn)漱抓,布局控件的布局流程基本上跟上述流程是相似的表锻。

2.6 使用場(chǎng)景

在一些需要表格展示的場(chǎng)景中,可以使用Table控件乞娄。

3. Wrap

A widget that displays its children in multiple horizontal or vertical runs.

3.1 簡(jiǎn)介

看簡(jiǎn)介瞬逊,其實(shí)Wrap實(shí)現(xiàn)的效果,F(xiàn)low可以很輕松仪或,而且可以更加靈活的實(shí)現(xiàn)出來(lái)确镊。

3.2 布局行為

Flow可以很輕易的實(shí)現(xiàn)Wrap的效果,但是Wrap更多的是在使用了Flex中的一些概念范删,某種意義上說(shuō)是跟Row蕾域、Column更加相似的。

單行的Wrap跟Row表現(xiàn)幾乎一致,單列的Wrap則跟Row表現(xiàn)幾乎一致旨巷。但Row與Column都是單行單列的巨缘,Wrap則突破了這個(gè)限制,mainAxis上空間不足時(shí)采呐,則向crossAxis上去擴(kuò)展顯示若锁。

從效率上講,F(xiàn)low肯定會(huì)比Wrap高斧吐,但是Wrap使用起來(lái)會(huì)方便一些又固。

3.3 繼承關(guān)系

Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Wrap

從繼承關(guān)系上看,Wrap與Flow都是繼承自MultiChildRenderObjectWidget煤率,F(xiàn)low可以實(shí)現(xiàn)Wrap的效果仰冠,但是兩者卻是單獨(dú)實(shí)現(xiàn)的,說(shuō)明兩者有很大的不同涕侈。

3.4 示例代碼

Wrap(
  spacing: 8.0, // gap between adjacent chips
  runSpacing: 4.0, // gap between lines
  children: <Widget>[
    Chip(
      avatar: CircleAvatar(
          backgroundColor: Colors.blue.shade900, child: new Text('AH', style: TextStyle(fontSize: 10.0),)),
      label: Text('Hamilton'),
    ),
    Chip(
      avatar: CircleAvatar(
          backgroundColor: Colors.blue.shade900, child: new Text('ML', style: TextStyle(fontSize: 10.0),)),
      label: Text('Lafayette'),
    ),
    Chip(
      avatar: CircleAvatar(
          backgroundColor: Colors.blue.shade900, child: new Text('HM', style: TextStyle(fontSize: 10.0),)),
      label: Text('Mulligan'),
    ),
    Chip(
      avatar: CircleAvatar(
          backgroundColor: Colors.blue.shade900, child: new Text('JL', style: TextStyle(fontSize: 10.0),)),
      label: Text('Laurens'),
    ),
  ],
)

示例代碼直接使用的官方文檔上的沪停,效果跟Flow的例子中相似。

Wrap樣例

3.5 源碼解析

構(gòu)造函數(shù)如下:

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

3.5.1 屬性解析

direction:主軸(mainAxis)的方向裳涛,默認(rèn)為水平木张。

alignment:主軸方向上的對(duì)齊方式,默認(rèn)為start端三。

spacing:主軸方向上的間距舷礼。

runAlignment:run的對(duì)齊方式。run可以理解為新的行或者列郊闯,如果是水平方向布局的話妻献,run可以理解為新的一行。

runSpacing:run的間距团赁。

crossAxisAlignment:交叉軸(crossAxis)方向上的對(duì)齊方式育拨。

textDirection:文本方向。

verticalDirection:定義了children擺放順序欢摄,默認(rèn)是down熬丧,見Flex相關(guān)屬性介紹。

3.5.2 源碼

我們來(lái)看下其布局代碼怀挠。

第一步析蝴,如果第一個(gè)child為null,則將其設(shè)置為最小尺寸绿淋。

RenderBox child = firstChild;
if (child == null) {
  size = constraints.smallest;
  return;
}

第二步闷畸,根據(jù)direction、textDirection以及verticalDirection屬性吞滞,計(jì)算出相關(guān)的mainAxis佑菩、crossAxis是否需要調(diào)整方向,以及主軸方向上的限制。

double mainAxisLimit = 0.0;
bool flipMainAxis = false;
bool flipCrossAxis = false;
switch (direction) {
  case Axis.horizontal:
    childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
    mainAxisLimit = constraints.maxWidth;
    if (textDirection == TextDirection.rtl)
      flipMainAxis = true;
    if (verticalDirection == VerticalDirection.up)
      flipCrossAxis = true;
    break;
  case Axis.vertical:
    childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
    mainAxisLimit = constraints.maxHeight;
    if (verticalDirection == VerticalDirection.up)
      flipMainAxis = true;
    if (textDirection == TextDirection.rtl)
      flipCrossAxis = true;
    break;
}

第三步倘待,計(jì)算出主軸以及交叉軸的區(qū)域大小疮跑。

while (child != null) {
  child.layout(childConstraints, parentUsesSize: true);
  final double childMainAxisExtent = _getMainAxisExtent(child);
  final double childCrossAxisExtent = _getCrossAxisExtent(child);
  if (childCount > 0 && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
    mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
    crossAxisExtent += runCrossAxisExtent;
    if (runMetrics.isNotEmpty)
      crossAxisExtent += runSpacing;
    runMetrics.add(new _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
    runMainAxisExtent = 0.0;
    runCrossAxisExtent = 0.0;
    childCount = 0;
  }
  runMainAxisExtent += childMainAxisExtent;
  if (childCount > 0)
    runMainAxisExtent += spacing;
  runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
  childCount += 1;
  final WrapParentData childParentData = child.parentData;
  childParentData._runIndex = runMetrics.length;
  child = childParentData.nextSibling;
}

第四步组贺,根據(jù)direction設(shè)置Wrap的尺寸凸舵。

switch (direction) {
  case Axis.horizontal:
    size = constraints.constrain(new Size(mainAxisExtent, crossAxisExtent));
    containerMainAxisExtent = size.width;
    containerCrossAxisExtent = size.height;
    break;
  case Axis.vertical:
    size = constraints.constrain(new Size(crossAxisExtent, mainAxisExtent));
    containerMainAxisExtent = size.height;
    containerCrossAxisExtent = size.width;
    break;
}

第五步,根據(jù)runAlignment計(jì)算出每一個(gè)run之間的距離失尖,幾種屬性的差異啊奄,之前文章介紹過,在此就不做詳細(xì)闡述掀潮。

final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
double runLeadingSpace = 0.0;
double runBetweenSpace = 0.0;
switch (runAlignment) {
  case WrapAlignment.start:
    break;
  case WrapAlignment.end:
    runLeadingSpace = crossAxisFreeSpace;
    break;
  case WrapAlignment.center:
    runLeadingSpace = crossAxisFreeSpace / 2.0;
    break;
  case WrapAlignment.spaceBetween:
    runBetweenSpace = runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
    break;
  case WrapAlignment.spaceAround:
    runBetweenSpace = crossAxisFreeSpace / runCount;
    runLeadingSpace = runBetweenSpace / 2.0;
    break;
  case WrapAlignment.spaceEvenly:
    runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
    runLeadingSpace = runBetweenSpace;
    break;
}

第六步菇夸,根據(jù)alignment計(jì)算出每一個(gè)run中child的主軸方向上的間距。

  switch (alignment) {
    case WrapAlignment.start:
      break;
    case WrapAlignment.end:
      childLeadingSpace = mainAxisFreeSpace;
      break;
    case WrapAlignment.center:
      childLeadingSpace = mainAxisFreeSpace / 2.0;
      break;
    case WrapAlignment.spaceBetween:
      childBetweenSpace = childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
      break;
    case WrapAlignment.spaceAround:
      childBetweenSpace = mainAxisFreeSpace / childCount;
      childLeadingSpace = childBetweenSpace / 2.0;
      break;
    case WrapAlignment.spaceEvenly:
      childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
      childLeadingSpace = childBetweenSpace;
      break;
  }

最后一步仪吧,調(diào)整child的位置庄新。

  while (child != null) {
    final WrapParentData childParentData = child.parentData;
    if (childParentData._runIndex != i)
      break;
    final double childMainAxisExtent = _getMainAxisExtent(child);
    final double childCrossAxisExtent = _getCrossAxisExtent(child);
    final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
    if (flipMainAxis)
      childMainPosition -= childMainAxisExtent;
    childParentData.offset = _getOffset(childMainPosition, crossAxisOffset + childCrossAxisOffset);
    if (flipMainAxis)
      childMainPosition -= childBetweenSpace;
    else
      childMainPosition += childMainAxisExtent + childBetweenSpace;
    child = childParentData.nextSibling;
  }

  if (flipCrossAxis)
    crossAxisOffset -= runBetweenSpace;
  else
    crossAxisOffset += runCrossAxisExtent + runBetweenSpace;

我們大致梳理一下布局的流程。

  • 如果第一個(gè)child為null薯鼠,則將Wrap設(shè)置為最小尺寸择诈,布局結(jié)束;
  • 根據(jù)direction出皇、textDirection以及verticalDirection屬性羞芍,計(jì)算出mainAxis、crossAxis是否需要調(diào)整方向郊艘;
  • 計(jì)算出主軸以及交叉軸的區(qū)域大泻煽啤;
  • 根據(jù)direction設(shè)置Wrap的尺寸纱注;
  • 根據(jù)runAlignment計(jì)算出每一個(gè)run之間的距離畏浆;
  • 根據(jù)alignment計(jì)算出每一個(gè)run中child的主軸方向上的間距
  • 調(diào)整每一個(gè)child的位置。

3.6 使用場(chǎng)景

對(duì)于一些需要按寬度或者高度狞贱,讓child自動(dòng)換行布局的場(chǎng)景刻获,可以使用,但是Wrap可以滿足的場(chǎng)景斥滤,F(xiàn)low一定可以實(shí)現(xiàn)将鸵,只不過會(huì)復(fù)雜很多,但是相對(duì)的會(huì)靈活以及高效很多佑颇。

4. 后話

筆者建了一個(gè)Flutter學(xué)習(xí)相關(guān)的項(xiàng)目顶掉,Github地址,里面包含了筆者寫的關(guān)于Flutter學(xué)習(xí)相關(guān)的一些文章挑胸,會(huì)定期更新痒筒,也會(huì)上傳一些學(xué)習(xí)Demo,歡迎大家關(guān)注。

5. 參考

  1. Flow class
  2. Table class
  3. Wrap class
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末簿透,一起剝皮案震驚了整個(gè)濱河市移袍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌老充,老刑警劉巖葡盗,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異啡浊,居然都是意外死亡觅够,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門巷嚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)喘先,“玉大人,你說(shuō)我怎么就攤上這事廷粒【秸” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵坝茎,是天一觀的道長(zhǎng)涤姊。 經(jīng)常有香客問我,道長(zhǎng)景东,這世上最難降的妖魔是什么砂轻? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮斤吐,結(jié)果婚禮上搔涝,老公的妹妹穿的比我還像新娘。我一直安慰自己和措,他們只是感情好庄呈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著派阱,像睡著了一般诬留。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贫母,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天文兑,我揣著相機(jī)與錄音,去河邊找鬼腺劣。 笑死绿贞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的橘原。 我是一名探鬼主播籍铁,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼涡上,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拒名?” 一聲冷哼從身側(cè)響起吩愧,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎增显,沒想到半個(gè)月后雁佳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甸怕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年甘穿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腮恩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梢杭。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秸滴,靈堂內(nèi)的尸體忽然破棺而出武契,到底是詐尸還是另有隱情,我是刑警寧澤荡含,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布咒唆,位于F島的核電站,受9級(jí)特大地震影響释液,放射性物質(zhì)發(fā)生泄漏全释。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一误债、第九天 我趴在偏房一處隱蔽的房頂上張望浸船。 院中可真熱鬧,春花似錦寝蹈、人聲如沸李命。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)封字。三九已至,卻和暖如春耍鬓,著一層夾襖步出監(jiān)牢的瞬間阔籽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工牲蜀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笆制,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓各薇,卻偏偏與公主長(zhǎng)得像项贺,于是被迫代替她去往敵國(guó)和親君躺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容