返回

flutter布局方式解析

2023-07-25 by 章勇

四棵树

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@immutable
abstract class Widget extends DiagnosticableTree {
/// Initializes [key] for subclasses.
const Widget({ this.key });

......
/// Inflates this configuration to a concrete instance.
///
/// A given widget can be included in the tree zero or more times. In particular
/// a given widget can be placed in the tree multiple times. Each time a widget
/// is placed in the tree, it is inflated into an [Element], which means a
/// widget that is incorporated into the tree multiple times will be inflated
/// multiple times.
@protected
@factory
Element createElement();

......
}

Element由Widget创建,子类实现时每次都是new一个新对象返回,所以一个Widget可以在多个地方使用而不会有冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
///
/// See also:
///
/// * [MultiChildRenderObjectWidget], which configures a [RenderObject] with
/// a single list of children.
/// * [SlottedMultiChildRenderObjectWidgetMixin], which configures a
/// [RenderObject] that organizes its children in different named slots.
abstract class RenderObjectWidget extends Widget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const RenderObjectWidget({ super.key });

/// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass.
@override
@factory
RenderObjectElement createElement();

/// Creates an instance of the [RenderObject] class that this
/// [RenderObjectWidget] represents, using the configuration described by this
/// [RenderObjectWidget].
///
/// This method should not do anything with the children of the render object.
/// That should instead be handled by the method that overrides
/// [RenderObjectElement.mount] in the object rendered by this object's
/// [createElement] method. See, for example,
/// [SingleChildRenderObjectElement.mount].
@protected
@factory
RenderObject createRenderObject(BuildContext context);

/// Copies the configuration described by this [RenderObjectWidget] to the
/// given [RenderObject], which will be of the same type as returned by this
/// object's [createRenderObject].
///
/// This method should not do anything to update the children of the render
/// object. That should instead be handled by the method that overrides
/// [RenderObjectElement.update] in the object rendered by this object's
/// [createElement] method. See, for example,
/// [SingleChildRenderObjectElement.update].
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

/// A render object previously associated with this widget has been removed
/// from the tree. The given [RenderObject] will be of the same type as
/// returned by this object's [createRenderObject].
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

RenderObject是RenderObjectWidget创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {

......

@protected
ContainerLayer? get layer {
assert(!isRepaintBoundary || _layerHandle.layer == null || _layerHandle.layer is OffsetLayer);
return _layerHandle.layer;
}

@protected
set layer(ContainerLayer? newLayer) {
assert(
!isRepaintBoundary,
'Attempted to set a layer to a repaint boundary render object.\n'
'The framework creates and assigns an OffsetLayer to a repaint '
'boundary automatically.',
);
_layerHandle.layer = newLayer;
}

final LayerHandle<ContainerLayer> _layerHandle = LayerHandle<ContainerLayer>();

......

@protected
void performLayout();

......

布局思想

首先,上层 widget 向下层 widget 传递约束条件;
然后,下层 widget 向上层 widget 传递大小信息。
最后,上层 widget 决定下层 widget 的位置。

严格约束(Tight)

指定大小

1
2
3
4
5
BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;

宽松约束(Loose)

限制了最大,可以在宽高可以在[0, max]之间

1
2
3
4
5
BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.width,
minHeight = 0.0,
maxHeight = size.height;

实践

https://flutter.cn/docs/ui/layout/constraints

Alt text

1
2
3
4
5
6
7
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(
width: double.infinity, height: double.infinity),
child: widget.examples[count - 1]
)
)

ConstrainedBox对应的RenderObject是RenderConstrainedBox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  @override
void performLayout() {
final BoxConstraints constraints = this.constraints;
if (child != null) {
child!.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
size = child!.size;
} else {
size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
}
}

......

BoxConstraints enforce(BoxConstraints constraints) {
return BoxConstraints(
minWidth: clampDouble(minWidth, constraints.minWidth, constraints.maxWidth),
maxWidth: clampDouble(maxWidth, constraints.minWidth, constraints.maxWidth),
minHeight: clampDouble(minHeight, constraints.minHeight, constraints.maxHeight),
maxHeight: clampDouble(maxHeight, constraints.minHeight, constraints.maxHeight),
);
}

......

double clampDouble(double x, double min, double max) {
assert(min <= max && !max.isNaN && !min.isNaN);
if (x < min) {
return min;
}
if (x > max) {
return max;
}
if (x.isNaN) {
return max;
}
return x;
}

说明ConstrainedBox的大小不会超过父级给它的边界
那么父级给了它什么样的边界呢
this.constraints来自上一级Expanded

1
2
3
4
5
6
7
8
9
10
class Expanded extends Flexible {
/// Creates a widget that expands a child of a [Row], [Column], or [Flex]
/// so that the child fills the available space along the flex widget's
/// main axis.
const Expanded({
super.key,
super.flex,
required super.child,
}) : super(fit: FlexFit.tight);
}

有时候,RenderObject需要在其子节点中存储一些数据,比如用于布局的一些参数,或者和其他子节点之间的关系。为此,Flutter提供了ParentData,用于存储父节点的一些信息。每个RenderObject都有这个成员变量,该成员在setupParentData方法中初始化。子类如果需要ParentData的某个子类,需要重写该方法,并在该方法中对ParentData进行初始化。
Expanded不提供RenderObject,它通过为子节点添加ParentData,让Expanded的父节点知道ConstraintBox如何布局,这就是为什么RenderObject树的数量和Widget数不是一一对应的。

1
2
3
4
5
6
7
8
late final double minChildExtent;
switch (_getFit(child)) {
case FlexFit.tight:
assert(maxChildExtent < double.infinity);
minChildExtent = maxChildExtent;
case FlexFit.loose:
minChildExtent = 0.0;
}

Alt text
Center对应的performLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;

if (child != null) {
child!.layout(constraints.loosen(), parentUsesSize: true);
size = constraints.constrain(Size(
shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity,
shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity,
));
alignChild();
} else {
size = constraints.constrain(Size(
shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity,
));
}
}

顺便看下Center怎么定位子节点位置

1
2
3
4
5
Offset alongOffset(Offset other) {
final double centerX = other.dx / 2.0;
final double centerY = other.dy / 2.0;
return Offset(centerX + x * centerX, centerY + y * centerY);
}

按照这个思路可以把后面所有的布局例子都看一遍