探究这个问题的契机是这样一小段代码:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("test"),
),
body: Container(
color: Colors.red,
child:AspectRatio(
aspectRatio: 2,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
)
);
}
写这一段是本来想要测试一下AsPectRatio的用法,想象中出现的画面应该是全屏红色,包裹着一个100*200的蓝色方块。但是运行出来的效果:
挠头到头秃,为什么里面那一层Container设置的大小不管用了呢??之前的工作中用到Container时有些奇怪的地方,例如设置大小不如预期啊之类的,但是我一直没去深究,也没去看源码,于是我决定先把AspectRatio的事情放到一边,先把这个Container的事情搞明白再说。
这时候我的同事跟我分享了一篇文章,https://mp.weixin.qq.com/s/nkjPIgNRazW56bHuh0PEXQ,我获取到的有效信息是:
原文深入浅出,我这个小白一下子就明白了其中的道理,可是我想知道,这在代码里具体是怎么操作的呢?父控件对子控件的约束Constraints在代码里是怎么告诉子控件的呢?子控件的大小又是怎么告诉父控件的呢?这个时候,灵光一闪,我打开了DevTools窗口,发现了一个宝贝~
之前几乎没有用过DevTools,RenderObject之前倒是在大神帖子里看过分析源码什么的,但是没有去接触过看了也就忘了。现在看来,向下传递Constraints和向上传递Size原来是RenderObject做的事情。于是重新看了大神们分析的RenderObject的帖子,再对照分析我这个小demo,顿时茅塞顿开,现复盘如下。
首先,Container的build源码:
@override
Widget build(BuildContext context) {
Widget? current = child;
if (child == null && (constraints == null || !constraints!.isTight)) {
current = LimitedBox(
maxWidth: 0.0,
maxHeight: 0.0,
child: ConstrainedBox(constraints: const BoxConstraints.expand()),
);
}
if (alignment != null)
current = Align(alignment: alignment!, child: current);
final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = Padding(padding: effectivePadding, child: current);
if (color != null)
current = ColoredBox(color: color!, child: current);
if (clipBehavior != Clip.none) {
assert(decoration != null);
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration!,
),
clipBehavior: clipBehavior,
child: current,
);
}
if (decoration != null)
current = DecoratedBox(decoration: decoration!, child: current);
if (foregroundDecoration != null) {
current = DecoratedBox(
decoration: foregroundDecoration!,
position: DecorationPosition.foreground,
child: current,
);
}
if (constraints != null)
current = ConstrainedBox(constraints: constraints!, child: current);
if (margin != null)
current = Padding(padding: margin!, child: current);
if (transform != null)
current = Transform(transform: transform!, child: current, alignment: transformAlignment);
return current!;
}
1.分析第一层Container
在这个Container中,我只给它了一个color属性,所以它的build函数实际上返回的是一个ColoredBox .
RenderObject的获取方法:
由此知道只有RenderObjectWidget才会有RenderObject,而Container是StatelessWidget,所以第一层的Container只有靠ColoredBox去获取RenderObject去进一步渲染布局。
这时候ColorBox的RenderObject的constraints是(0.0<=w<=1920.0, 0.0<=h<=913.0) (高度是1080减去了AppBar和网页浏览器顶部占的像素),在RenderObject的layout()中,调用了perfromLayout函数。往上找,找到RenderColoredBox最终用到的是RenderProxyBoxMixin中的prefromLayout
在这个函数中,调用了子RenderObject的layout方法,并将constraints(0.0<=w<=1920.0, 0.0<=h<=913.0)传递了过去。
2.分析第二层AspectRatio
它的RenderObject是RenderAspectRatio,刚才被调了layout(),依旧找到它的perfromLayout:
可以看到刚才被传入了constraints(0.0<=w<=1920.0, 0.0<=h<=913.0),根据这个constraints,计算出了size=Size(1826,913)(w:h=2:1),这时候调用了子renderObject的layout,并SizeSize(1826,913)作为严格约束传递了过去。
3.分析第三层Container
第三层Container我给它填了三个参数,宽高和颜色,build函数实际上返回的是一个ConstrainedBox包裹着ColoredBox。并将Size(100,100)作为一个严格约束传递给了ConstrainedBox
3.1分析ConstrainedBox
那么上一层RenderAspectRatio调用的layout就是RenderConstrainedBox,那么按照惯例找到它的preformLayout:
看到这里面做了一件事,就是将Size(100,100)这个约束强行卡在上层RenderObject传过来的约束里,因为上层传过来的是一个严格约束:Size(1826,913),所以就直接用了Size(1826,913),调用子RenderObject,将这个约束传递给了子RenderObject。
3.2分析ColoredBox
同样找到它的performLayout,它已经没有child了,所以就直接调用了performResize,开始设置高度。到此,从上往下传递约束已经结束。
现在开始从下往上传递size.
找到它的performResize函数,可以看到它的size是constraints得到的,所以得到Size(1826,913)就是最内层的RenderObject的大小啦。
往上,在RenderConstrainedBox的performLayout中可以看到它的size就是子RenderObject的大小,Size(1826,913);
往上,在RenderAspectRatio的performLayout中看到,它的size已经在刚才传递constraints之前就已经计算好了,就是Size(1826,913);
往上,在RenderColoredBox的performLayout中可以看到它的size就是子RenderObject的大小,Size(1826,913);
这就是我探索最开始的疑问为什么container设置的长和宽没有起作用的全过程啦。
当当当当!!未解之谜终于被解开!
参考的别人的源码分析:https://www.cnblogs.com/lxlx1798/articles/11174987.html