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

    二,其它布局处理

    • 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,
             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(
               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 > 0 && 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 = 0;
      }
      runMainAxisExtent += childMainAxisExtent;
      if (childCount > 0)
        runMainAxisExtent += spacing;
        runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
        childCount += 1;
        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 > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
        break;
         case WrapAlignment.spaceAround:
           runBetweenSpace = crossAxisFreeSpace / runCount;
           runLeadingSpace = runBetweenSpace / 2.0;
        break;
         case WrapAlignment.spaceEvenly:
           runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
           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 > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
          break;
        case WrapAlignment.spaceAround:
           childBetweenSpace = mainAxisFreeSpace / childCount;
           childLeadingSpace = childBetweenSpace / 2.0;
          break;
        case WrapAlignment.spaceEvenly:
           childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
           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布局

  • 相关阅读:
    javascript和jquery模拟select框
    CSS使用菱形◆制作气泡对话框(兼容IE6 IE9)
    给网站添加显示在标签页上的小图标
    mysql设置utf8_unicode_ci字符集php页面输出??乱码的解决方法
    推荐一个学习的站
    ubuntu 12.04 安装 nginx+php+mysql web服务器
    PHP解决[function.filegetcontents]: failed to open stream: HTTP request failed!
    svn 操作命令
    svn 端口查看
    php函数parse_url()
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11093053.html
Copyright © 2011-2022 走看看