一,概述  

  Flutter中拥有30多种预定义的布局widget,常用的有Container、Padding、Center、Flex、Row、Colum、ListView、GridView。按照《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(按宽高自动换行布局)

二,列表和表格处理布局组件

  • ListView(列表)  

    • 介绍
      ListView是一个非常常用的控件,涉及到数据列表展示的,一般情况下都会选用该控件。ListView跟GridView相似,基本上是一个slivers里面只包含一个SliverList的CustomScrollView。
    • 布局行为
      ListView在主轴方向可以滚动,在交叉轴方向,则是填满ListView。
    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > ScrollView > BoxScrollView > ListView

      看继承关系可知,这是一个组合控件。ListView跟GridView类似,都是继承自BoxScrollView。

    • 构造函数
      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>[],
      })

      同时也提供了如下额外的三种构造方法,方便开发者使用。

      ListView.builder
      ListView.separated
      ListView.custom
    • 使用场景

      ListView使用场景太多了,一般涉及到列表展示的,一般都会选择ListView。

      但是需要注意一点,ListView的标准构造函数适用于数目比较少的场景,如果数目比较多的话,最好使用ListView.builder。

      ListView的标准构造函数会将所有item一次性创建,而ListView.builder会创建滚动到屏幕上显示的item。

    • 参数解析

      ListView大部分属性同GridView,想了解的读者可以看一下下面所写的GridView相关的内容。这里只介绍一个属性

      itemExtent:ListView在滚动方向上每个item所占的高度值。

  • GridView(网格)
    • 介绍

      GridView在移动端上非常的常见,就是一个滚动的多列列表,实际的使用场景也非常的多。
    • 布局行为
      GridView的布局行为不复杂,本身是尽量占满空间区域,布局行为上完全继承自ScrollView。
    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > ScrollView > BoxScrollView > GridView
    • 构造函数
      GridView({
      Key key,
      Axis scrollDirection = Axis.vertical,
      bool reverse = false,
      ScrollController controller,
      bool primary,
      ScrollPhysics physics,
      bool shrinkWrap = false,
      EdgeInsetsGeometry padding,
      @required this.gridDelegate,
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      double cacheExtent,
      List<Widget> children = const <Widget>[],
      })

      同时也提供了如下额外的四种构造方法,方便开发者使用。

      GridView.builder
      GridView.custom
      GridView.count
      GridView.extent
    • 参数解析

      scrollDirection:滚动的方向,有垂直和水平两种,默认为垂直方向(Axis.vertical)。

      reverse:默认是从上或者左向下或者右滚动的,这个属性控制是否反向,默认值为false,不反向滚动。

      controller:控制child滚动时候的位置。

      primary:是否是与父节点的PrimaryScrollController所关联的主滚动视图。

      physics:滚动的视图如何响应用户的输入。

      shrinkWrap:滚动方向的滚动视图内容是否应该由正在查看的内容所决定。

      padding:四周的空白区域。

      gridDelegate:控制GridView中子节点布局的delegate。

      cacheExtent:缓存区域。

  • Table(表格)
    • 介绍

      每一种移动端布局中都会有一种table布局,这种控件太常见了。至于其表现形式,完全可以借鉴其他移动端的,通俗点讲,就是表格。
    • 布局行为

      表格的每一行的高度,由其内容决定,每一列的宽度,则由columnWidths属性单独控制。

    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > Table
    • 构造函数
      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,
      })
    • 参数解析

      columnWidths:设置每一列的宽度。

      defaultColumnWidth:默认的每一列宽度值,默认情况下均分。

      textDirection:文字方向,一般无需考虑。

      border:表格边框。

      defaultVerticalAlignment:每一个cell的垂直方向的alignment。

      总共包含5种:

      • top:被放置在的顶部;
      • middle:垂直居中;
      • bottom:放置在底部;
      • baseline:文本baseline对齐;
      • fill:充满整个cell。

      textBaseline:defaultVerticalAlignment为baseline的时候,会用到这个属性。

三,常用示例

  • ListView(列表)

    /**
    * ListView
    * 第一个展示四行文字
    */
    class MyListView extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return new ListView(
    shrinkWrap: true,
    padding: EdgeInsets.all(20.0),
    children: <Widget>[
    new Text('I\m dedicationg every day to you'),
    new Text('Domestic life was never quite my style'),
    new Text('When you smile, you knock me out, I fall apart'),
    new Text('And I thought I was so smart')
    ],
    );
    }
    }

    效果图:

    源码解析:

    @override
    Widget buildChildLayout(BuildContext context) {
    if (itemExtent != null) {
    return new SliverFixedExtentList(
    delegate: childrenDelegate,
    itemExtent: itemExtent,
    );
    }
    return new SliverList(delegate: childrenDelegate);
    }

    ListView标准构造布局代码如上所示,底层是用到的SliverList去实现的。ListView是一个slivers里面只包含一个SliverList的CustomScrollView。源码这块儿可以参考GridView,在此不做更多的说明。

  • GridView(网格)  
    /**
    * GridView
    * 代码直接用了Creating a Grid List中的例子,创建了一个2列总共100个子节点的列表。
    */
    class MyGridView extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return new GridView.count(
    crossAxisCount: ,
    children: List.generate(, (index){
    return new Center(
    child: new Text(
    'Item $index',
    style: Theme.of(context).textTheme.headline,
    ),
    );
    },
    ),
    );
    }
    }

    效果图

    源码解析:

    @override
    Widget build(BuildContext context) {
    final List<Widget> slivers = buildSlivers(context);
    final AxisDirection axisDirection = getDirection(context); final ScrollController scrollController = primary
    ? PrimaryScrollController.of(context)
    : controller;
    final Scrollable scrollable = new Scrollable(
    axisDirection: axisDirection,
    controller: scrollController,
    physics: physics,
    viewportBuilder: (BuildContext context, ViewportOffset offset) {
    return buildViewport(context, offset, axisDirection, slivers);
    },
    );
    return primary && scrollController != null
    ? new PrimaryScrollController.none(child: scrollable)
    : scrollable;
    }

    上面这段代码是ScrollView的build方法,GridView就是一个特殊的ScrollView。GridView本身代码没有什么,基本上都是ScrollView上的东西,主要会涉及到Scrollable、Sliver、Viewport等内容,这些内容比较多,因此源码就先略了,后面单独出一篇文章对ScrollView进行分析吧。

  • Table(表格)
    Table(
    columnWidths: const <int, TableColumnWidth>{
    : FixedColumnWidth(50.0),
    : FixedColumnWidth(100.0),
    : FixedColumnWidth(50.0),
    : 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'),
    ],
    ),
    ],
    )

    效果图:

    (1)样例其实并不复杂,FlowDelegate需要自己实现child的绘制,其实大多数时候就是位置的摆放。上面例子中,对每个child按照给定的margin值,进行排列,如果超出一行,则在下一行进行布局。
    (2)另外,对这个例子多做一个说明,对于上述child宽度的变化,这个例子是没问题的,如果每个child的高度不同,则需要对代码进行调整,具体的调整是换行的时候,需要根据上一行的最大高度来确定下一行的起始y坐标。

    源码解析:

    我们直接来看其布局源码:

    第一步,当行或者列为0的时候,将自身尺寸设为0x0。

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

    第二步,根据textDirection值,设置方向,一般在阿拉伯语系中,一些文本都是从右往左现实的,平时使用时,不需要去考虑这个属性。

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

    第三步,设置每一个cell的尺寸。

    for (int x = ; x < columns; x += ) {
    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则进行相关设置。

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

    第五步,根据alignment,调整child的位置。

    for (int x = ; x < columns; x += ) {
    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;
    }
    }
    }

    最后一步,则是根据每一行的宽度以及每一列的高度,设置Table的尺寸。

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

    最后梳理一下整个的布局流程:

    当行或者列为0的时候,将自身尺寸设为0x0;
    根据textDirection进行相关设置;
    设置cell的尺寸;
    如果设置了baseline,则进行相关设置;
    根据alignment设置cell垂直方向的位置;
    设置Table的尺寸。
    如果经常关注系列文章的读者,可能会发现,布局控件的布局流程基本上跟上述流程是相似的。  

四,参考  

《Flutter学习之认知基础组件》
《Flutter布局》

  

【Flutter学习】页面布局之列表和表格处理的更多相关文章

  1. 前端学习笔记--CSS样式--列表和表格

    1.列表 2.表格 odd:奇数  even:偶数

  2. flutter column row布局的列表自适应宽高

    mainAxisSize: MainAxisSize.min

  3. 【Flutter学习】页面布局之其它布局处理

    一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...

  4. 【Flutter学习】页面布局之宽高尺寸处理

    一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...

  5. 【Flutter学习】页面布局之基础布局组件

    一,概述 Flutter中拥有30多种预定义的布局widget,常用的有Container.Padding.Center.Flex.Row.Colum.ListView.GridView.按照< ...

  6. Flutter学习六之实现一个带筛选的列表页面

    上期实现了一个网络轮播图的效果,自定义了一个轮播图组件,继承自StatefulWidget,我们知道Flutter中并没有像Android中activity的概念.页面见的跳转是通过路由从一个全屏组件 ...

  7. CSS3与页面布局学习总结(四)——页面布局大全

    一.负边距与浮动布局 1.1.负边距 所谓的负边距就是margin取负值的情况,如margin:-100px,margin:-100%.当一个元素与另一个元素margin取负值时将拉近距离.常见的功能 ...

  8. CSS3与页面布局学习总结(四)——页面布局大全BFC、定位、浮动、7种垂直居中方法

    目录 一.BFC与IFC 1.1.BFC与IFC概要 1.2.如何产生BFC 1.3.BFC的作用与特点 二.定位 2.2.relative 2.3.absolute 2.4.fixed 2.5.z- ...

  9. Flutter学习笔记(23)--多个子元素的布局Widget(Rwo、Column、Stack、IndexedStack、Table、Wrap)

    如需转载,请注明出处:Flutter学习笔记(23)--多个子元素的布局Widget(Rwo.Column.Stack.IndexedStack.Table.Wrap) 上一篇梳理了拥有单个子元素布局 ...

随机推荐

  1. k-近邻算法(kNN)测试算法:作为完整程序验证分类器

    #测试算法:作为完整程序验证分类器 def datingClassTest(): hoRatio = 0.10 #设置测试集比重,前10%作为测试集,后90%作为训练集 datingDataMat,d ...

  2. LOJ 2978 「THUSCH 2017」杜老师——bitset+线性基+结论

    题目:https://loj.ac/problem/2978 题解:https://www.cnblogs.com/Paul-Guderian/p/10248782.html 第 i 个数的 bits ...

  3. 转载:IDEA配置SVN及使用

    转自:https://blog.csdn.net/zwj1030711290/article/details/80687365 1.安装svn客户端 之前用myEcplise只需要插件,现在IDEA需 ...

  4. bzoj 2242: [SDOI2011]计算器 & BSGS算法笔记

    这题的主要难点在于第三问该如何解决 于是就要知道BSGS是怎样的一种方法了 首先BSGS是meet in the middle的一种(戳下面看) http://m.blog.csdn.net/blog ...

  5. spark sql correlated scalar subqueries must be aggregated 错误解决

    最近在客户中使用spark sql 做一些表报处理,但是在做数据关联时,老是遇到 “correlated scalar subqueries must be aggregated” 错误 举一个例子, ...

  6. 阿里云使用ssl免费证书

    购买免费证书 购买之后 申请证书 该域名必须添加一条TXT记录 根据提示添加记录 下载证书 我用的nginx做的映射,所以下载nginx nginx安装自行百度 将下载的文件解压到nginx目录下(创 ...

  7. VMWARE ESXI 虚拟硬盘的格式:精简置备、厚置备延迟置零、厚置备置零

    精简置备(thin): 精 简配置就是无论磁盘分配多大,实际占用存储大小是现在使用的大小,即用多少算多少.当客户机有输入输出的时候,VMkernel首先分配需要的空间并进行 清零操作,也就是说如果使用 ...

  8. c++primer,自定义一个复数类

    #include<iostream> #include<string> #include<vector> #include<algorithm> #in ...

  9. QTP——改变Excel的单元格颜色

    Public Function QTP_Change_Color(pathway,sheetname,x,y,color)        Dim srcData,srcDoc,sp1,sp2,num, ...

  10. python-django之cookie及session

    Cookie Cookie的由来 Http协议是无状态的 无状态的意思是每次都是独立的请求存在,它的执行情况和结果与前面的请求和后面的请求都无直接关系,它不会受到前面的请求响应情况直接影响,也不会直接 ...