一,概述  

  Flutter中拥有30多种预定义的布局widget,常用的有ContainerPaddingCenterFlexRowColumListViewGridView。按照《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. SQL SERVER内部函数大全

    SQL SERVER内部函数是SQL数据库中非常重要的一类函数,下面就为您介绍SQL SERVER内部函数,如果您对此方面感兴趣的话,不妨一看. SQL SERVER内部函数: select @@CO ...

  2. php strtolower()函数 语法

    php strtolower()函数 语法 作用:把所有字符转换为小写.大理石量具 语法:strtolower(string) 参数: 参数 描述 string 必须,规定要转换的字符串 说明:str ...

  3. 4412 i2c驱动

    1.Linux主机驱动和外设驱动分离思想 外设驱动→API→主机驱动→板机逻辑--具体的i2c设备(camera,ts,eeprom等等) 2.主机驱动 根据控制器硬件手册,操作具体的寄存器,产生波形 ...

  4. 4412 最简Linux驱动

    最简Linux驱动 必备的头文件 • Linux头文件位置– 类似#include <linux/module.h>的头文件,它们是在Linux源码目录下的include/linux/mo ...

  5. 【HDOJ6601】Keen On Everything But Triangle(主席树)

    题意:给定一个长为n的序列,有q次询问,每次询问[l,r]这段区间内挑三个数,能组成的三角形的最大周长,无解输出-1 n,q<=1e5,a[i]<=1e9 思路:题解写法和我的不太一样 先 ...

  6. dumpsys, traceView调试命令

    1. dumpsys dumpsys cpuinfo: 打印cpu使用情况: dumpsys meminfo: 打印内存使用率情况: dumpsys activity: 打印所有活动的信息: dump ...

  7. Redis的高级特性一览

    更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 应用场景 缓存系统:用于缓解数据库的高并发压力 计数器:使用Redis原子操作,用于社交网络的转发数,评论数,粉丝数 ...

  8. (选做)实现mypwd

    选做 实现mypwd 实验内容: 1.学习pwd命令. 2.研究pwd实现需要的系统调用(man -k; grep),写出伪代码. 3.实现mypwd. 4.测试mypwd. 实验步骤: 学习pwd命 ...

  9. 2016年Esri技术公开课全年资料分享

    大家好,2016年的公开课活动在上周全部结束,感谢大家的支持. 2016年的公开课共进行20期,共有24位讲师参与,公开课视频播放.课件下载次数累计超10万次,在这里衷心的感谢大家的积极参与和分享精神 ...

  10. Understanding identities in IIS

    Understanding identities in IIS https://support.microsoft.com/en-my/help/4466942/understanding-ident ...