一,概述  

  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. LOJ 6432 「PKUSC2018」真实排名——水题

    题目:https://loj.ac/problem/6432 如果不选自己,设自己的值是 x ,需要让 “ a<x && 2*a>=x ” 的非 x 的值不被选:如果选自己 ...

  2. git 小错误

    (一)在本地直接修改文件,提交后出现(master|REBASE 1/2).由于文件冲突所以导致各种报错. 在git pull --rebase origin master后 error: Pulli ...

  3. SPRING CLOUD微服务DEMO-上篇

    目录 1. 微服务架构 2. 远程调用方式 2.1 RPC/RMI 2.2 Http 2.3 如何选择 3. Http客户端工具 3.1 RestTemplate 4. Spring Boot 搭建项 ...

  4. delphi vlc 安装bug 处理编译错误"0" is an invalid value for the "DebugInformation" parameter of the "DCC"

    处理编译错误"0" is an invalid value for the "DebugInformation" parameter of the "DCC" [摘要:http://blog.csdn ...

  5. 小程序UI自动化(一):appium小程序自动化尝试

    appium 进行 小程序自动化尝试: 由于工作中进行app自动化用的是appium,故首先尝试用appium进行小程序自动化,以美团小程序为例(python脚本实现) 一.配置基础信息 启动微信ap ...

  6. OSX 创建 randisk(或称 tmpfs)

    创建步骤: #!/bin/bash ramdisk_size_in_mb= mount_point=/private/tmp ramdisk_size_in_sectors=$((${ramdisk_ ...

  7. Cocos2d-x中使用的数据容器类

    |   版权声明:本文为博主原创文章,未经博主允许不得转载. 在计算机的数据结构中,有着数组,链表,堆栈,队列,树,图,哈希表等一些结构.在面向对象的语言中,这些结构被封装成了特定的类,而这些类就是容 ...

  8. SAP Smartforms打印输出条形码 及相关问题

    最近凭证打印需要附加打印条形码,遂做了一个小例子,结果还出现了很多的小问题,按领导的话说,这就是经验! 首先:SE73 -> 系统条形码 -> 更改 -> 创建 -> 选择 N ...

  9. springCloud的使用07-----消息总线(spring cloud bus)

    spring cloud bus 将分布式的节点用轻量的消息代理连接起来.可用于广播配置文件的更改或服务之间的通讯,也可以用于监控. spring cloud bus 默认只支持rabbitmq和ka ...

  10. 【转】在配置静态IP的时候遇到 :bringing up interface eth0 : error unknown connection

    首先这是动态ip配置成功的结果 接下来切换到root用户来配置静态的 按照静态ip的配置方法配置好文件后(具体过程这里就不多加说明) 然后保存退出 当我们重启网卡的时候问题来了(因为本人有点强迫症,多 ...