上篇我们以Container,Center为例通过源码分析找到了为什么Container设置的宽高会不生效的原因,本篇将着重介绍Center是如何将child绘制到中间的,以此为例来探究flutter父widget对子widget的布局,以及子widget如何绘制的?
下面我们看看官网举的一个例子:如果一个 widget 中包含了一个具有 padding 的 Column,并且要对 Column 的子 widget 进行如下的布局:
那么谈判将会像这样:
Widget: “嘿!我的父级。我的约束是多少?”
Parent: “你的宽度必须在 80 到 300 像素之间,高度必须在 30 到 85 之间。”
Widget: “嗯…我想要 5 个像素的内边距,这样我的子级能最多拥有 290 个像素宽度和 75 个像素高度。”
Widget: “嘿,我的第一个子级,你的宽度必须要在 0 到 290,长度在 0 到 75 之间。”
First child: “OK,那我想要 290 像素的宽度,20 个像素的长度。”
Widget: “嗯…由于我想要将我的第二个子级放在第一个子级下面,所以我们仅剩 55 个像素的高度给第二个子级了。”
Widget: “嘿,我的第二个子级,你的宽度必须要在 0 到 290,长度在 0 到 55 之间。”
Second child: “OK,那我想要 140 像素的宽度,30 个像素的长度。”
Widget: “很好。我的第一个子级将被放在 x: 5 & y: 5 的位置,而我的第二个子级将在 x: 80 & y: 25 的位置。”
Widget: “嘿,我的父级,我决定我的大小为 300 像素宽度,60 像素高度。”
根据以上我们能得出如下的结论:
- 一个 widget 仅在其父级给其约束的情况下才能决定自身的大小。这意味着 widget 通常情况下 不能任意获得其想要的大小。
- 一个 widget 无法知道,也不需要决定其在屏幕中的位置。因为它的位置是由其父级决定的。
- 当轮到父级决定其大小和位置的时候,同样的也取决于它父级自身的父级。所以,在不考虑整棵树的情况下,几乎不可能精确定义任何 widget 的大小和位置。
- 如果子级想要拥有和父级不同的大小,然而父级没有足够的空间对其进行布局的话,子级的设置的大小可能会不生效。 这时请明确指定它的对齐方式
上一节我们找到了Center对应的render RenderPositionedBox,在它的performLayout里我们找到了约束的传递。要想将widget正确的绘制出来,不仅要有约束,还需要具体的位置信息。说到绘制我们第一想到的就是看看RenderPositionedBox的paint方法都做了啥?
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
final BoxParentData childParentData = child!.parentData! as BoxParentData;
context.paintChild(child!, childParentData.offset + offset);
}
咦!这么简单,看样子就是传了个偏移量,也就是确定child的坐标。但是这个里面的childParentData是从哪来的呢?
前面的分析我们知道约束是在performLayout()方法里传递过来的,那么childParentData会不会也是在这个方法里呢,毕竟跟布局是相关的。看下源码:
@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(); // align 是不是对位置的确认呢?
} else {
size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity));
}
}
这里我们看到alignChild()这个方法,它是不是对位置的确认呢?看下源码不就知道了吗
@protected
void alignChild() {
_resolve(); // 重新赋值_resolvedAlignment
assert(child != null);
assert(!child!.debugNeedsLayout);
assert(child!.hasSize);
assert(hasSize);
assert(_resolvedAlignment != null);
/// 在此处对childParentData做了相应的处理
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
}
///alongOffset ,由于是Aligment.center,所以x,y都为0
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);
}
以上方法就得出了Container对应的render->RenderConstraintBox的parentData了。有了约束,确定了大小,有了位置,那么是不是就可以绘制上去了。我们继续看下Container对应的render,因为我们给Container设置了颜色,并且给了它Center的父widget,所以Container会对应两个RenderObjectWidget,一个是ColoredBox,一个是ConstrainedBox,并且ColoredBox是作为ConstrainedBox的child的。
先看下ConstrainedBox对应的renderView的paint 方法,没有做处理,直接去执行child的paint方法,也就是ColoredBox对应的RenderObject-->_RenderColoredBox 的paint方法,方法如下:
@override
void paint(PaintingContext context, Offset offset) {
// It's tempting to want to optimize out this `drawRect()` call if the
// color is transparent (alpha==0), but doing so would be incorrect. See
// https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for
// a good description of why.
if (size > Size.zero) {
context.canvas.drawRect(offset & size, Paint()..color = color);
}
if (child != null) {
context.paintChild(child!, offset);
}
}
至此一个100*100的红色Container就被绘制出来了。我们注意到child!.layout(constraints.loosen(), parentUsesSize: true)这个方法,发现当parentUsesSize为true时,父组件会使用child的size,在child的size发生变化时,会重新布局。
总结: