一,概述  

  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。
    • 继承关系
      1. Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > ScrollView > BoxScrollView > ListView

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

    • 构造函数
      1. ListView({
      2. Key key,
      3. Axis scrollDirection = Axis.vertical,
      4. bool reverse = false,
      5. ScrollController controller,
      6. bool primary,
      7. ScrollPhysics physics,
      8. bool shrinkWrap = false,
      9. EdgeInsetsGeometry padding,
      10. this.itemExtent,
      11. bool addAutomaticKeepAlives = true,
      12. bool addRepaintBoundaries = true,
      13. double cacheExtent,
      14. List<Widget> children = const <Widget>[],
      15. })

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

      1. ListView.builder
      2. ListView.separated
      3. ListView.custom
    • 使用场景

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

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

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

    • 参数解析

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

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

  • GridView(网格)
    • 介绍

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

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

      1. GridView.builder
      2. GridView.custom
      3. GridView.count
      4. GridView.extent
    • 参数解析

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

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

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

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

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

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

      padding:四周的空白区域。

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

      cacheExtent:缓存区域。

  • Table(表格)
    • 介绍

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

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

    • 继承关系
      1. Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > Table
    • 构造函数
      1. Table({
      2. Key key,
      3. this.children = const <TableRow>[],
      4. this.columnWidths,
      5. this.defaultColumnWidth = const FlexColumnWidth(1.0),
      6. this.textDirection,
      7. this.border,
      8. this.defaultVerticalAlignment = TableCellVerticalAlignment.top,
      9. this.textBaseline,
      10. })
    • 参数解析

      columnWidths:设置每一列的宽度。

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

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

      border:表格边框。

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

      总共包含5种:

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

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

三,常用示例

  • ListView(列表)

    1. /**
    2. * ListView
    3. * 第一个展示四行文字
    4. */
    5. class MyListView extends StatelessWidget {
    6. @override
    7. Widget build(BuildContext context) {
    8. // TODO: implement build
    9. return new ListView(
    10. shrinkWrap: true,
    11. padding: EdgeInsets.all(20.0),
    12. children: <Widget>[
    13. new Text('I\m dedicationg every day to you'),
    14. new Text('Domestic life was never quite my style'),
    15. new Text('When you smile, you knock me out, I fall apart'),
    16. new Text('And I thought I was so smart')
    17. ],
    18. );
    19. }
    20. }

    效果图:

    源码解析:

    1. @override
    2. Widget buildChildLayout(BuildContext context) {
    3. if (itemExtent != null) {
    4. return new SliverFixedExtentList(
    5. delegate: childrenDelegate,
    6. itemExtent: itemExtent,
    7. );
    8. }
    9. return new SliverList(delegate: childrenDelegate);
    10. }

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

  • GridView(网格)  
    1. /**
    2. * GridView
    3. * 代码直接用了Creating a Grid List中的例子,创建了一个2列总共100个子节点的列表。
    4. */
    5. class MyGridView extends StatelessWidget {
    6. @override
    7. Widget build(BuildContext context) {
    8. // TODO: implement build
    9. return new GridView.count(
    10. crossAxisCount: ,
    11. children: List.generate(, (index){
    12. return new Center(
    13. child: new Text(
    14. 'Item $index',
    15. style: Theme.of(context).textTheme.headline,
    16. ),
    17. );
    18. },
    19. ),
    20. );
    21. }
    22. }

    效果图

    源码解析:

    1. @override
    2. Widget build(BuildContext context) {
    3. final List<Widget> slivers = buildSlivers(context);
    4. final AxisDirection axisDirection = getDirection(context);
    5.  
    6. final ScrollController scrollController = primary
    7. ? PrimaryScrollController.of(context)
    8. : controller;
    9. final Scrollable scrollable = new Scrollable(
    10. axisDirection: axisDirection,
    11. controller: scrollController,
    12. physics: physics,
    13. viewportBuilder: (BuildContext context, ViewportOffset offset) {
    14. return buildViewport(context, offset, axisDirection, slivers);
    15. },
    16. );
    17. return primary && scrollController != null
    18. ? new PrimaryScrollController.none(child: scrollable)
    19. : scrollable;
    20. }

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

  • Table(表格)
    1. Table(
    2. columnWidths: const <int, TableColumnWidth>{
    3. : FixedColumnWidth(50.0),
    4. : FixedColumnWidth(100.0),
    5. : FixedColumnWidth(50.0),
    6. : FixedColumnWidth(100.0),
    7. },
    8. border: TableBorder.all(color: Colors.red, width: 1.0, style: BorderStyle.solid),
    9. children: const <TableRow>[
    10. TableRow(
    11. children: <Widget>[
    12. Text('A1'),
    13. Text('B1'),
    14. Text('C1'),
    15. Text('D1'),
    16. ],
    17. ),
    18. TableRow(
    19. children: <Widget>[
    20. Text('A2'),
    21. Text('B2'),
    22. Text('C2'),
    23. Text('D2'),
    24. ],
    25. ),
    26. TableRow(
    27. children: <Widget>[
    28. Text('A3'),
    29. Text('B3'),
    30. Text('C3'),
    31. Text('D3'),
    32. ],
    33. ),
    34. ],
    35. )

    效果图:

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

    源码解析:

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

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

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

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

    1. switch (textDirection) {
    2. case TextDirection.rtl:
    3. positions[columns - ] = 0.0;
    4. for (int x = columns - ; x >= ; x -= )
    5. positions[x] = positions[x+] + widths[x+];
    6. _columnLefts = positions.reversed;
    7. tableWidth = positions.first + widths.first;
    8. break;
    9. case TextDirection.ltr:
    10. positions[] = 0.0;
    11. for (int x = ; x < columns; x += )
    12. positions[x] = positions[x-] + widths[x-];
    13. _columnLefts = positions;
    14. tableWidth = positions.last + widths.last;
    15. break;
    16. }

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

    1. for (int x = ; x < columns; x += ) {
    2. final int xy = x + y * columns;
    3. final RenderBox child = _children[xy];
    4. if (child != null) {
    5. final TableCellParentData childParentData = child.parentData;
    6. childParentData.x = x;
    7. childParentData.y = y;
    8. switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
    9. case TableCellVerticalAlignment.baseline:
    10. child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
    11. final double childBaseline = child.getDistanceToBaseline(textBaseline, onlyReal: true);
    12. if (childBaseline != null) {
    13. beforeBaselineDistance = math.max(beforeBaselineDistance, childBaseline);
    14. afterBaselineDistance = math.max(afterBaselineDistance, child.size.height - childBaseline);
    15. baselines[x] = childBaseline;
    16. haveBaseline = true;
    17. } else {
    18. rowHeight = math.max(rowHeight, child.size.height);
    19. childParentData.offset = new Offset(positions[x], rowTop);
    20. }
    21. break;
    22. case TableCellVerticalAlignment.top:
    23. case TableCellVerticalAlignment.middle:
    24. case TableCellVerticalAlignment.bottom:
    25. child.layout(new BoxConstraints.tightFor(width: widths[x]), parentUsesSize: true);
    26. rowHeight = math.max(rowHeight, child.size.height);
    27. break;
    28. case TableCellVerticalAlignment.fill:
    29. break;
    30. }
    31. }
    32. }

    第四步,如果有baseline则进行相关设置。

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

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

    1. for (int x = ; x < columns; x += ) {
    2. final int xy = x + y * columns;
    3. final RenderBox child = _children[xy];
    4. if (child != null) {
    5. final TableCellParentData childParentData = child.parentData;
    6. switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
    7. case TableCellVerticalAlignment.baseline:
    8. if (baselines[x] != null)
    9. childParentData.offset = new Offset(positions[x], rowTop + beforeBaselineDistance - baselines[x]);
    10. break;
    11. case TableCellVerticalAlignment.top:
    12. childParentData.offset = new Offset(positions[x], rowTop);
    13. break;
    14. case TableCellVerticalAlignment.middle:
    15. childParentData.offset = new Offset(positions[x], rowTop + (rowHeight - child.size.height) / 2.0);
    16. break;
    17. case TableCellVerticalAlignment.bottom:
    18. childParentData.offset = new Offset(positions[x], rowTop + rowHeight - child.size.height);
    19. break;
    20. case TableCellVerticalAlignment.fill:
    21. child.layout(new BoxConstraints.tightFor(width: widths[x], height: rowHeight));
    22. childParentData.offset = new Offset(positions[x], rowTop);
    23. break;
    24. }
    25. }
    26. }

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

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

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

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

四,参考  

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. 双十一高并发场景背后的数据库RDS技术揭秘

    [战报]11月11日聚石塔(阿里云数据库RDS产品形态)峰值QPS突破X00w,Proxy 峰值QPS超过X00w. 双十一就要来了,全世界都为其疯狂,但是在双十一抢购中经常会出现几万人抢一个红包或者 ...

  2. 【LeetCode 75】颜色分类

    题目链接 [题解] 维护一个左边界l和一个右边界r 其中0..l-1都是'0' 而 r+1..n-1都是'2' 我们令i=l;i<=r; 枚举每一个a[i]; ①如果a[i]=2.那么把a[i] ...

  3. 【Linux】shell脚本参数传递

    这里介绍参数传递的两种方式. 方式一:$0,$1,$2... 采用$0,$1,$2..等方式获取脚本命令行传入的参数 $0:脚本名称 $1....: 参数 例子: #编写一个shell $ vim t ...

  4. 10.14.1-linux设置时间等

    设置时间[root@wen /]# date -s "20171014 15:42:00"2017年 10月 14日 星期六 15:42:00 CST 格式化时间[root@wen ...

  5. python中得字典和常用函数总结

    字典是python中一种常见得数据类型,用{}表示,并且以键值对得形式存放数据. dic={},其中得key键值是不可变得,类型可以是字符串.其中,列表,字典不可以作为键,键值是不可变得.字符串,元组 ...

  6. iPad如何恢复

    iPad在有网络连接的情况下,可以通过iCloud进行恢复. 没有连接WiFi的情况下,只能通过USB连接才能恢复. ①下载最新版本的iTunes:https://support.apple.com/ ...

  7. 【原】webpack--loaders,主要解释为什么需要loaders和注意事项

    Why需要loaders? webpack开箱即用只支持JS和JSON两种文件类型,但是比如css.less,还有目前市场上比较新的语法糖jsx,怎么处理呢? 通过Loaders去支持其他文件类型并且 ...

  8. Php安装时出现的问题处理

    问题从这里开始,我们一步一步说明: cd /usr/local/src/ tar zxvf php-5.5.6.tar.gz cd php-5.5.6 ./configure \ //执行当前目录下软 ...

  9. 提高redis cluster集群的安全性,增加密码验证

    节点设置密码 1.修改配置文件 在配置文件里面增加密码选项,一定要加上masterauth,不然Redirected的时候会失败. masterauth redispassword requirepa ...

  10. 洛谷P3366 【模板】最小生成树(LCT)

    [模板]最小生成树 题目传送门 解题思路 用LCT来维护最小生成树. 除了把各顶点作为节点外,每条边也都视为一个节点.对于要加入的边\(e\),检查其两顶点\(x\)和\(y\)是否在同一棵树中,如果 ...