一,概述  

  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(按宽高自动换行布局)

二,其它布局处理

  • Transform(矩阵转换)

    • 介绍

      Transform在介绍Container的时候有提到过,就是做矩阵变换的。Container中矩阵变换就是使用的Transform。
    • 布局行为
      有过其他平台经验的,对Transform应该不会陌生。可以对child做平移、旋转、缩放等操作。
    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Transform
    • 构造函数
      const Transform({
      Key key,
      @required this.transform,
      this.origin,
      this.alignment,
      this.transformHitTests = true,
      Widget child,
      })

      上面是其默认的构造函数,Transform也提供下面三种构造函数:

      Transform.rotate
      Transform.translate
      Transform.scale
    • 参数含义
      • transform:一个4x4的矩阵。不难发现,其他平台的变换矩阵也都是四阶的。一些复合操作,仅靠三维是不够的,必须采用额外的一维来补充,感兴趣的同学可以自行搜索了解。
      • origin:旋转点,相对于左上角顶点的偏移。默认旋转点事左上角顶点。

      • alignment:对齐方式。

      • transformHitTests:点击区域是否也做相应的改变。

          

  • Baseline(基准线布局)
    • 介绍

      Baseline这个控件,做过移动端开发的都会了解过,一般文字排版的时候,可能会用到它。它的作用很简单,根据child的baseline,来调整child的位置。例如两个字号不一样的文字,希望底部在一条水平线上,就可以使用这个控件,是一个非常基础的控件。

      关于字符的Baseline,可以看下下面这张图,这具体就涉及到了字体排版,感兴趣的同学可以自行了解。

    • 布局行为

      Baseline控件布局行为分为两种情况:

      • 如果child有baseline,则根据child的baseline属性,调整child的位置;
      • 如果child没有baseline,则根据child的bottom,来调整child的位置。
    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Baseline
    • 构造函数
      const Baseline({
      Key key,
      @required this.baseline,
      @required this.baselineType,
      Widget child
      })
    • 参数含义 

      baseline:baseline数值,必须要有,从顶部算。

      baselineType:bseline类型,也是必须要有的,目前有两种类型:

      • alphabetic:对齐字符底部的水平线;
      • ideographic:对齐表意字符的水平线。
  • Offstage(控制是否显示组件)
    • 介绍
      Offstage的作用很简单,通过一个参数,来控制child是否显示,日常使用中也算是比较常用的控件。
    • 布局行为

      Offstage的布局行为完全取决于其offstage参数

      • 当offstage为true,当前控件不会被绘制在屏幕上,不会响应点击事件,也不会占用空间;
      • 当offstage为false,当前控件则跟平常用的控件一样渲染绘制;

      另外,当Offstage不可见的时候,如果child有动画,应该手动停掉,Offstage并不会停掉动画。

    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > SingleChildRenderObjectWidget > Offstage
    • 构造函数
      const Offstage(
      {
        Key key,
      this.offstage = true,
      Widget child
      }
      )
    • 参数含义
      offstage:默认为true,也就是不显示,当为flase的时候,会显示该控件。 
  • Wrap(按宽高自动换行布局)
    • 介绍
      其实Wrap实现的效果,Flow可以很轻松,而且可以更加灵活的实现出来。
    • 布局行为

      Flow可以很轻易的实现Wrap的效果,但是Wrap更多的是在使用了Flex中的一些概念,某种意义上说是跟Row、Column更加相似的。

      单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Row表现几乎一致。但Row与Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。

      从效率上讲,Flow肯定会比Wrap高,但是Wrap使用起来会方便一些。

    • 继承关系
      Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Wrap
    • 构造函数
      Wrap({
      Key key,
      this.direction = Axis.horizontal,
      this.alignment = WrapAlignment.start,
      this.spacing = 0.0,
      this.runAlignment = WrapAlignment.start,
      this.runSpacing = 0.0,
      this.crossAxisAlignment = WrapCrossAlignment.start,
      this.textDirection,
      this.verticalDirection = VerticalDirection.down,
      List<Widget> children = const <Widget>[],
      })
    • 参数含义 
      • direction:主轴(mainAxis)的方向,默认为水平。
      • alignment:主轴方向上的对齐方式,默认为start。

      • spacing:主轴方向上的间距。

      • runAlignment:run的对齐方式。run可以理解为新的行或者列,如果是水平方向布局的话,run可以理解为新的一行。

      • runSpacing:run的间距。

      • crossAxisAlignment:交叉轴(crossAxis)方向上的对齐方式。

      • textDirection:文本方向。

      • verticalDirection:定义了children摆放顺序,默认是down,见Flex相关属性介绍。

三,使用实例

  • Transform(矩阵转换)

    Center(
    child: Transform(
    transform: Matrix4.rotationZ(0.3),
    child: Container(
    color: Colors.blue,
    width: 100.0,
    height: 100.0,
    ),
    ),
    )

    将Container绕z轴旋转了

    效果图:

    源码解析

    我们来看看它的绘制代码:

    if (child != null) {
    final Matrix4 transform = _effectiveTransform;
    final Offset childOffset = MatrixUtils.getAsTranslation(transform);
    if (childOffset == null)
    context.pushTransform(needsCompositing, offset, transform, super.paint);
    else
    super.paint(context, offset + childOffset);
    }

    整个绘制代码不复杂,如果child有偏移的话,则将两个偏移相加,进行绘制。如果child没有偏移的话,则按照设置的offset、transform进行绘制。

    使用场景:这个控件算是较常见的控件,很多平移、旋转、缩放都可以使用的到。如果只是单纯的进行变换的话,用Transform比用Container效率会更高。

  • Baseline(基准线布局)
    new Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: <Widget>[
    new Baseline(
    baseline: 50.0,
    baselineType: TextBaseline.alphabetic,
    child: new Text(
    'TjTjTj',
    style: new TextStyle(
    fontSize: 20.0,
    textBaseline: TextBaseline.alphabetic,
    ),
    ),
    ),
    new Baseline(
    baseline: 50.0,
    baselineType: TextBaseline.alphabetic,
    child: new Container(
    width: 30.0,
    height: 30.0,
    color: Colors.red,
    ),
    ),
    new Baseline(
    baseline: 50.0,
    baselineType: TextBaseline.alphabetic,
    child: new Text(
    'RyRyRy',
    style: new TextStyle(
    fontSize: 35.0,
    textBaseline: TextBaseline.alphabetic,
    ),
    ),
    ),
    ],
    )

    效果图:

    源码解析:

    我们来看看源码中具体计算尺寸的这段代码

    child.layout(constraints.loosen(), parentUsesSize: true);
    final double childBaseline = child.getDistanceToBaseline(baselineType);
    final double actualBaseline = baseline;
    final double top = actualBaseline - childBaseline;
    final BoxParentData childParentData = child.parentData;
    childParentData.offset = new Offset(0.0, top);
    final Size childSize = child.size;
    size = constraints.constrain(new Size(childSize.width, top + childSize.height));

    getDistanceToBaseline这个函数是获取baseline数值的,存在的话,就取这个值,不存在的话,则取其高度。

    整体的计算过程
    (1)获取child的 baseline 值;
    (2)计算出top值,其为 baseline - childBaseline,这个值有可能为负数;
    (3)计算出Baseline控件尺寸,宽度为child的,高度则为 top + childSize.height。

  • Offstage(控制是否显示组件)
    Column(
    children: <Widget>[
    new Offstage(
    offstage: offstage,
    child: Container(color: Colors.blue, height: 100.0),
    ),
    new CupertinoButton(
    child: Text("点击切换显示"),
    onPressed: () {
    setState(() {
    offstage = !offstage;
    });
    },
    ),
    ],
    )

    源码解析

    我们先来看下Offstage的computeIntrinsicSize相关的方法:

    @override
    double computeMinIntrinsicWidth(double height) {
    if (offstage)
    return 0.0;
    return super.computeMinIntrinsicWidth(height);
    }

    可以看到,当offstage为true的时候,自身的最小以及最大宽高都会被置为0.0。

    接下来我们来看下其hitTest方法:

    @override
    bool hitTest(HitTestResult result, { Offset position }) {
    return !offstage && super.hitTest(result, position: position);
    }

    当offstage为true的时候,也不会去执行。

    最后我们来看下其paint方法:

    @override
    void paint(PaintingContext context, Offset offset) {
    if (offstage)
    return;
    super.paint(context, offset);
    }

    当offstage为true的时候直接返回,不绘制了。

    到此,跟上面所说的布局行为对应上了。我们一定要清楚一件事情,Offstage并不是通过插入或者删除自己在widget tree中的节点,来达到显示以及隐藏的效果,而是通过设置自身尺寸、不响应hitTest以及不绘制,来达到展示与隐藏的效果。

  • Wrap(按宽高自动换行布局)
    Wrap(
    spacing: 8.0, // gap between adjacent chips
    runSpacing: 4.0, // gap between lines
    children: <Widget>[
    Chip(
    avatar: CircleAvatar(
    backgroundColor: Colors.blue.shade900, child: new Text('AH', style: TextStyle(fontSize: 10.0),)),
    label: Text('Hamilton'),
    ),
    Chip(
    avatar: CircleAvatar(
    backgroundColor: Colors.blue.shade900, child: new Text('ML', style: TextStyle(fontSize: 10.0),)),
    label: Text('Lafayette'),
    ),
    Chip(
    avatar: CircleAvatar(
    backgroundColor: Colors.blue.shade900, child: new Text('HM', style: TextStyle(fontSize: 10.0),)),
    label: Text('Mulligan'),
    ),
    Chip(
    avatar: CircleAvatar(
    backgroundColor: Colors.blue.shade900, child: new Text('JL', style: TextStyle(fontSize: 10.0),)),
    label: Text('Laurens'),
    ),
    ],
    )

    效果图:

    源码解析

    我们来看下其布局代码。

    第一步,如果第一个child为null,则将其设置为最小尺寸。

    RenderBox child = firstChild;
    if (child == null) {
    size = constraints.smallest;
    return;
    }

    第二步,根据direction、textDirection以及verticalDirection属性,计算出相关的mainAxis、crossAxis是否需要调整方向,以及主轴方向上的限制。

    double mainAxisLimit = 0.0;
    bool flipMainAxis = false;
    bool flipCrossAxis = false;
    switch (direction) {
    case Axis.horizontal:
    childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth);
    mainAxisLimit = constraints.maxWidth;
    if (textDirection == TextDirection.rtl)
    flipMainAxis = true;
    if (verticalDirection == VerticalDirection.up)
    flipCrossAxis = true;
    break;
    case Axis.vertical:
    childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight);
    mainAxisLimit = constraints.maxHeight;
    if (verticalDirection == VerticalDirection.up)
    flipMainAxis = true;
    if (textDirection == TextDirection.rtl)
    flipCrossAxis = true;
    break;
    }

    第三步,计算出主轴以及交叉轴的区域大小。

    while (child != null) {
    child.layout(childConstraints, parentUsesSize: true);
    final double childMainAxisExtent = _getMainAxisExtent(child);
    final double childCrossAxisExtent = _getCrossAxisExtent(child);
    if (childCount > && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
    mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
    crossAxisExtent += runCrossAxisExtent;
    if (runMetrics.isNotEmpty)
    crossAxisExtent += runSpacing;
    runMetrics.add(new _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
    runMainAxisExtent = 0.0;
    runCrossAxisExtent = 0.0;
    childCount = ;
    }
    runMainAxisExtent += childMainAxisExtent;
    if (childCount > )
    runMainAxisExtent += spacing;
    runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
    childCount += ;
    final WrapParentData childParentData = child.parentData;
    childParentData._runIndex = runMetrics.length;
    child = childParentData.nextSibling;
    }

    第四步,根据direction设置Wrap的尺寸。

    switch (direction) {
    case Axis.horizontal:
    size = constraints.constrain(new Size(mainAxisExtent, crossAxisExtent));
    containerMainAxisExtent = size.width;
    containerCrossAxisExtent = size.height;
    break;
    case Axis.vertical:
    size = constraints.constrain(new Size(crossAxisExtent, mainAxisExtent));
    containerMainAxisExtent = size.height;
    containerCrossAxisExtent = size.width;
    break;
    }

    第五步,根据runAlignment计算出每一个run之间的距离,几种属性的差异,之前文章介绍过,在此就不做详细阐述。

    final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
    double runLeadingSpace = 0.0;
    double runBetweenSpace = 0.0;
    switch (runAlignment) {
    case WrapAlignment.start:
    break;
    case WrapAlignment.end:
    runLeadingSpace = crossAxisFreeSpace;
    break;
    case WrapAlignment.center:
    runLeadingSpace = crossAxisFreeSpace / 2.0;
    break;
    case WrapAlignment.spaceBetween:
    runBetweenSpace = runCount > ? crossAxisFreeSpace / (runCount - ) : 0.0;
    break;
    case WrapAlignment.spaceAround:
    runBetweenSpace = crossAxisFreeSpace / runCount;
    runLeadingSpace = runBetweenSpace / 2.0;
    break;
    case WrapAlignment.spaceEvenly:
    runBetweenSpace = crossAxisFreeSpace / (runCount + );
    runLeadingSpace = runBetweenSpace;
    break;
    }

    第六步,根据alignment计算出每一个run中child的主轴方向上的间距。

    switch (alignment) {
    case WrapAlignment.start:
    break;
    case WrapAlignment.end:
    childLeadingSpace = mainAxisFreeSpace;
    break;
    case WrapAlignment.center:
    childLeadingSpace = mainAxisFreeSpace / 2.0;
    break;
    case WrapAlignment.spaceBetween:
    childBetweenSpace = childCount > ? mainAxisFreeSpace / (childCount - ) : 0.0;
    break;
    case WrapAlignment.spaceAround:
    childBetweenSpace = mainAxisFreeSpace / childCount;
    childLeadingSpace = childBetweenSpace / 2.0;
    break;
    case WrapAlignment.spaceEvenly:
    childBetweenSpace = mainAxisFreeSpace / (childCount + );
    childLeadingSpace = childBetweenSpace;
    break;
    }

    最后一步,调整child的位置。

    while (child != null) {
    final WrapParentData childParentData = child.parentData;
    if (childParentData._runIndex != i)
    break;
    final double childMainAxisExtent = _getMainAxisExtent(child);
    final double childCrossAxisExtent = _getCrossAxisExtent(child);
    final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
    if (flipMainAxis)
    childMainPosition -= childMainAxisExtent;
    childParentData.offset = _getOffset(childMainPosition, crossAxisOffset + childCrossAxisOffset);
    if (flipMainAxis)
    childMainPosition -= childBetweenSpace;
    else
    childMainPosition += childMainAxisExtent + childBetweenSpace;
    child = childParentData.nextSibling;
    } if (flipCrossAxis)
    crossAxisOffset -= runBetweenSpace;
    else
    crossAxisOffset += runCrossAxisExtent + runBetweenSpace;

    我们大致梳理一下布局的流程。

    如果第一个child为null,则将Wrap设置为最小尺寸,布局结束;
    根据direction、textDirection以及verticalDirection属性,计算出mainAxis、crossAxis是否需要调整方向;
    计算出主轴以及交叉轴的区域大小;
    根据direction设置Wrap的尺寸;
    根据runAlignment计算出每一个run之间的距离;
    根据alignment计算出每一个run中child的主轴方向上的间距
    调整每一个child的位置。

四,参考  

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

【Flutter学习】页面布局之其它布局处理的更多相关文章

  1. 【Flutter学习】页面布局之列表和表格处理

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

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

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

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

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

  4. Flutter学习指南:UI布局和控件

    Flutter学习指南:UI布局和控件 - IT程序猿  https://www.itcodemonkey.com/article/11041.html

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

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

  6. Flutter学习笔记(22)--单个子元素的布局Widget(Container、Padding、Center、Align、FittedBox、Offstage、LimitedBox、OverflowBox、SizedBox)

    如需转载,请注明出处:Flutter学习笔记(22)--单个子元素的布局Widget(Container.Padding.Center.Align.FittedBox.Offstage.Limited ...

  7. web页面之响应式布局

    一.什么是响应式布局? 响应式布局是Ethan Marcotte在2010年5月份提出的一个概念,简而言之,就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本.这个概念是为解决移动互联网 ...

  8. Flex布局【弹性布局】学习

    先让我们看看在原来的学习中遇到的问题 之前在软件工程的大作业中,自己从零开始学习如何开发一个网站,从页面,到后台,当然数据库是大二的必修课 在学习如何编写一个静态页面的时候,完全是自学,自己摸索,所以 ...

  9. CSS3 Flex布局(伸缩布局盒模型)学习

    CSS3 Flex布局(伸缩布局盒模型)学习 转自:http://www.xifengxx.com/web-front-end/1408.html CSS2定义了四种布局:块布局.行内布局.表格布局盒 ...

随机推荐

  1. PHP curl_escape函数

    curl_escape — 对给定的字符串进行URL编码. 说明 string curl_escape ( resource $ch , string $str ) 该函数对给定的字符串进行URL编码 ...

  2. LYOI2016 Summer 一次函数 (线段树)

    题目描述 fqk 退役后开始补习文化课啦,于是他打开了数学必修一开始复习函数,他回想起了一次函数都是 f(x)=kx+b的形式,现在他给了你n个一次函数 fi(x)=kix+b,然后将给你m个操作,操 ...

  3. cordova打包apk流程

    一.打包 条件: 1.java-jdk 2.Android-sdk  ( 安装教程:https://blog.csdn.net/qq_36577136/article/details/80632674 ...

  4. python中的_ElementUnicodeResult是什么

    _ElementUnicodeResult在python中是字符串的一种,因为在python3中,字符串就是指以unicode编码规则存储的数据,而以其他方式如utf-8,ASCII编码方式存储的数据 ...

  5. LintCode之合并排序数组

    题目描述: 我的代码: public class Solution { /* * @param A: sorted integer array A * @param B: sorted integer ...

  6. 部署Jenkins完整记录

    Jenkins通过脚本任务触发,实现代码的自动化分发,是CI持续化集成环境中不可缺少的一个环节.下面对Jenkins环境的部署做一记录.-------------------------------- ...

  7. 在RedHat中安装新字体

    安装 下载这个字体. http://pan.baidu.com/s/1c23znaS 密码:tldo 在/usr/share/fonts/truetype/, 下建立一个新的目录 YaHei Cons ...

  8. ROM、RAM、DRAM、SRAM、FLASH的区别?

    在学习单片机的时候经常会被这些东西搞晕掉,什么ROM RAM FLASH EEPROM 等等......为了不被搞晕,做个笔记,不记得的时候过来看看. 下面是我在网上找的资料: ROM和RAM指的都是 ...

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

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

  10. Centos 7.3 配置Xmanager XDMCP

    我们通常需要远程桌面,这会带来很好的便利性,而Centos7的XDMCP配置过程发生了变化,添加了很多新特性,初期难免会不适应,但新系统终究还是不错的.下面看看Centos7下如何配置XManager ...