zoukankan      html  css  js  c++  java
  • 【Flutter学习】页面布局之列表和表格处理

    一,概述  

      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('Im 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: 2,
            children: List.generate(100, (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>{
          0: FixedColumnWidth(50.0),
          1: FixedColumnWidth(100.0),
          2: FixedColumnWidth(50.0),
          3: FixedColumnWidth(100.0),
        },
        border: TableBorder.all(color: Colors.red,  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 == 0) {
      size = constraints.constrain(const Size(0.0, 0.0));
      return;
      }

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

      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;
             }

      第三步,设置每一个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( 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( widths[x]), parentUsesSize: true);
           rowHeight = math.max(rowHeight, child.size.height);
      break;
      case TableCellVerticalAlignment.fill:
      break;
      }
      }
      }

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

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

      第五步,根据alignment,调整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( 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布局

      

  • 相关阅读:
    [SDOI2008]递归数列
    [SCOI2008]奖励关
    [SCOI2010]幸运数字
    [ZJOI2007]矩阵游戏
    [HAOI2006]旅行
    [ZJOI2008]泡泡堂
    [BZOJ1800][Ahoi2009]fly 飞行棋
    [POJ2288]Islands and Bridges
    [LUOGU] 3959 宝藏
    [BZOJ]1029: [JSOI2007]建筑抢修
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11091391.html
Copyright © 2011-2022 走看看