其他分享
首页 > 其他分享> > flutter —— 布局原理与约束

flutter —— 布局原理与约束

作者:互联网

一、布局模型

主要有两种布局模型:
① 基于 RenderBox 的盒模型布局。
② 基于 Sliver ( RenderSliver ) 按需加载列表布局。

两种布局方式在细节上略有差异,但大体流程相同,布局流程如下:

  1. 上层组件向下层组件传递约束(constraints)条件。
  2. 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
  3. 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。

二、盒模型的约束

基础约束对象

const BoxConstraints({
  this.minWidth = 0.0, //最小宽度
  this.maxWidth = double.infinity, //最大宽度
  this.minHeight = 0.0, //最小高度
  this.maxHeight = double.infinity //最大高度
})

多重约束

约束通常是收缩的,最小值以父级中最大的为准,最大值以父级中最小的为准。

ConstrainedBox(
  constraints: BoxConstraints(
    maxWidth: 30,
    maxHeight: 180,
  ),
  child: ConstrainedBox(
    constraints: BoxConstraints(maxWidth: 290, maxHeight: 30),
    child: SizedBox(
      width: 550,
      height: 550,
      child: redBox,
    ),
  ),
)

渲染的结果是 30,30 大小的正方形

SizedBox

严格约束

SizedBox(
  width: 80.0,
  height: 80.0,
  child: redBox
)

等价于

ConstrainedBox(
  constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
  child: redBox, 
)

三、去除组件约束

假如有一个组件 A,它的子组件是B,B 的子组件是 C,则 C 必须遵守 B 的约束,同时 B 必须遵守 A 的约束,但是 A 的约束不会直接约束到 C,除非B将A对它自己的约束透传给了C。 利用这个原理,就可以实现一个这样的 B 组件:

① B 组件中在布局 C 时不约束C(可以为无限大)。
② C 根据自身真实的空间占用来确定自身的大小。
③ B 在遵守 A 的约束前提下结合子组件的大小确定自身大小。

而这个 B组件就是 UnconstrainedBox 组件,也就是说UnconstrainedBox 的子组件将不再受到约束,大小完全取决于自己。

将 UnconstrainedBox 换成 Center 或者 Align 也是可以的
需要注意,UnconstrainedBox 虽然在其子组件布局时可以取消约束(子组件可以为无限大),但是 UnconstrainedBox 自身是受其父组件约束的,所以当 UnconstrainedBox 随着其子组件变大后,如果UnconstrainedBox 的大小超过它父组件约束时,也会导致溢出报错。

四、约束原理

例1:自定义 center 组件

class CustomCenter extends SingleChildRenderObjectWidget {
  const CustomCenter({Key? key, required Widget child})
      : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCustomCenter();
  }
}

class RenderCustomCenter extends RenderShiftedBox {
  RenderCustomCenter({RenderBox? child}) : super(child);

  @override
  void performLayout() {
    //1. 先对子组件进行layout,随后获取它的size
    child!.layout(
      constraints.loosen(), //将无限制的约束传递给子节点
      parentUsesSize: true, // 因为我们接下来要使用child的size,所以不能为false
    );
    //2.根据子组件的大小确定自身的大小
    size = constraints.constrain(Size(
      //如果父组件的宽度不限制,则使用子组件宽度,否则以父组件的最大宽度为准
      constraints.maxWidth == double.infinity
          ? child!.size.width
          : double.infinity,
      //如果父组件的高度不限制,则使用子组件高度,否则以父组件的最大高度为准
      constraints.maxHeight == double.infinity
          ? child!.size.height
          : double.infinity,
    ));

    // 3. 根据父节点子节点的大小,算出子节点在父节点中居中之后的偏移,然后将这个偏移保存在
    // 子节点的parentData中,在后续的绘制阶段,会用到。
    BoxParentData parentData = child!.parentData as BoxParentData;
    parentData.offset = ((size - child!.size) as Offset) / 2;
  }
}

例2:UnconstrainedBox 布局原理

  void performLayout() {
    final BoxConstraints constraints = this.constraints;
    final RenderBox? child = this.child;
    if (child != null) {
      final BoxConstraints childConstraints = constraintsTransform(constraints);
      assert(childConstraints != null);
      assert(childConstraints.isNormalized, '$childConstraints is not normalized');
      _childConstraints = childConstraints;
      // 将上层组件约束传递给下层组件,下组件确定自己的大小(并且使用子组件的大小)
      child.layout(childConstraints, parentUsesSize: true);
      // 上层组件通过 child.size 得到子组件大小,确定自己的大小(给 size 赋值即上报上层组件自己的大小)
      size = constraints.constrain(child.size);
      alignChild();
      final BoxParentData childParentData = child.parentData! as BoxParentData;
      _overflowContainerRect = Offset.zero & size;
      _overflowChildRect = childParentData.offset & child.size;
    } else {
      size = constraints.smallest;
      _overflowContainerRect = Rect.zero;
      _overflowChildRect = Rect.zero;
    }
    _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
  }

注:此处未设置子组件的偏移量。完整代码位于 RenderConstraintsTransformBox 中。

例3:Row、Column 组件布局原理

对于子级

以 Row 组件为例,则水平方向不限,垂直方向尽可能大(但不能超出父级约束)

对于自身

1)纵轴方向

大小来自子级最大的那个,但不能超出父级约束

2)主轴方向

① 如果主轴不是无限大,且 mainAxisSize = MainAxisSize.max(Row,Column 默认此设置),则主轴方向大小为父级最大约束。

② 如果主轴是无限大,则主轴方向大小为 allocatedSize,即子组件在主轴方向大小的和。

举例1:

Row 行内布局

Container(
  constraints: BoxConstraints(
    maxWidth: 210,
    maxHeight: 220,
  ),
  color: Colors.green,
  child: Row(
    children: [
      Container(
        width: 301,
        height: 102,
        color: Colors.red,
      ),
      Container(
        width: 50,
        height: 50,
        color: Colors.blue,
      ),
    ],
  ),
)

对子级约束是 宽度不限,高度最大220

对自身大小是 宽度210,高度102

举例2:

Row 设置 主轴 min

同样,自身大小也是 50

Row(
  mainAxisSize: MainAxisSize.min,
  children: [
    Container(
      width: 50,
      height: 50,
      color: Colors.blue,
    ),
  ],
)

举例3:

Row,其父级约束不限

由于父级主轴方向是不限宽度,最终其自身宽度为 50

Container(
  constraints: BoxConstraints(
    maxWidth: 210,
    maxHeight: 220,
  ),
  color: Colors.green,
  child: UnconstrainedBox(
    child: Container(
      constraints: BoxConstraints(
        maxWidth: double.infinity,
      ),
      child: Row(
        children: [
          Container(
            width: 50,
            height: 50,
            color: Colors.blue,
          ),
        ],
      ),
    ),
  ),
)

举例4:

对于两个嵌套的 Row,里面那个 Row 自身的宽度也是 50

Container(
  constraints: BoxConstraints(
    maxWidth: 210,
    maxHeight: 220,
  ),
  color: Colors.green,
  child: Row(
    children: [
      Row(
        children: [
          Container(
            width: 50,
            height: 50,
            color: Colors.blue,
          ),
        ],
      ),
    ],
  ),
)

举例5:

两个嵌套的 Row 里面使用 Expanded

报错:RenderFlex children have non-zero flex but incoming width constraints are unbounded.

即主轴方向上无限大时,子组件的 flex 不能大于 0。Flexible 或 Expanded 默认 flex 为 1。

Container(
  width: 300,
  height: 300,
  color: Colors.green,
  child: Row(
    children: [
      Row(
        children: [
          Expanded(
            child: Container(
              width: 100,
              height: 100,
              color: Colors.red,
            ),
          ),
        ],
      )
    ],
  ),
)

参考文档


https://book.flutterchina.club/chapter14/layout.html#_14-4-1-单子组件布局示例-customcenter

标签:size,约束,child,组件,原理,Row,flutter,constraints
来源: https://www.cnblogs.com/lemos/p/16524032.html