Flutter:了解无状态/有状态小部件如何布局其子部件



TL;DR

如果没有关于小部件的文档,我如何知道它的布局规则(它将向其父级请求什么大小,它将向子级传递什么约束)?

问题详细信息

我有这个非常基本的应用

void main() {
runApp(
Container(
color: Colors.red,
width: 20,
height: 20,
),
);
}

我原以为Container的宽度和高度是20,但我得到的Container填满了整个屏幕。

在flatter.dev上阅读这篇关于理解约束的文章,文章的最后一部分称为";学习特定小部件的布局规则&";,他们提到了如何通过找到createRenderObject方法然后找到performLayout方法来做到这一点。

然而,这种createRenderObject方法仅适用于RenderObjectWidget的子类。例如,在Transform小部件的代码中导航,我找到了返回RenderTransformcreateRenderObject,它扩展了RenderProxyBox,最终将performLayout实现为:

@override
void performLayout() {
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
size = child!.size;
} else {
size = computeSizeForNoChild(constraints);
}
}

我可以得出结论,由于这行size = child!.size;Transform小部件最终将采用其子部件的大小。

但在上述Container的情况下,则直接扩展为StatelessWidget。通过浏览其代码,我找不到方法performLayoutcreateRenderObject,我只能找到createElement,但我在与Container相关联的渲染树中寻找RenderObject,而不是元素。

问题

因此,问题是如何找到与无状态小部件/有状态小部件相关联的呈现对象,以便了解该小部件将给其子部件的布局规则,并在这种情况下自行遵循这些规则?

你有道理。我认为我的文章在这方面不准确。

小部件不需要创建RenderObject。相反,它可以使用其他小部件的组合来创建RenderObjects

如果一个小部件是其他小部件的组合,那么不必查看performLayout,只需查看该小部件的build方法即可了解它在做什么。在Container的情况下,这是其build方法:

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.maybeOf(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!, alignment: transformAlignment, child: current);
return current!;
}

正如您所看到的,它是根据其他小部件来定义的。这些小部件也可以根据其他小部件等进行定义。但在某个时候,你会看到创建RenderObjects的小部件。


关于Container不是20x20的原因,正如文章所解释的,这是因为尺寸是由父母设置的。因此,Container的大小是由Container的父级设置的,在本例中是屏幕。屏幕总是强迫它的孩子占据所有可用的空间,在这种情况下忽略了Container想要20x20的愿望。此处的修复方法是为Container提供另一个父级。一个允许CCD_ 32选择其自己的大小。例如,CenterAlign都会让这种情况发生,这就是为什么您可以通过执行以下操作来解决问题:

void main() {
runApp(
Center( 
child: Container(
color: Colors.red,
width: 20,
height: 20,
),),);
}

至于为什么屏幕会强迫它的孩子占据所有可用的空间:这正是Flutter创作者决定的方式。如果你深入研究Flutter的代码,你会在那里找到它。但你最好记住这个事实。

希望它能有所帮助!

Meh,不要太复杂。这里是我可以向您解释的最简单的事情,让我们将一个应用程序分为4个不同的层。

  1. 应用程序->可以使用MaterialApp创建材质应用程序
  2. 小工具页面->您可以根据需要在使用StatelessStatefulWidget之间进行选择。如果你需要动态的,有很多变化的状态,你可以使用StatefulWidget,也可以用StatelessWidget创建一个静态页面
  3. 脚手架->Widget页面必须返回一个scaffold才能形成一个素材页面,在这里你可以使用Scaffold,你可以添加appBar、fab、body、bottomNavigationBar&等等
  4. 小工具->这里的小部件可以是任何东西,它可以是脚手架的appBar,也可以是ListView、GridView或Custom小部件

所以你的代码必须是这样的,有点

/// This block is the first point of your application, this will run your application
void main() {
runApp(myApp());
}
/// Then you need an material app, this part should return your app
class MyApp extends StatelessWidget{
/// This is very important, both StatelessWidget / StatefullWidget 
/// always having a build method. It's should returning a Widget. But in this case we will return an material application
@override
Widget build(BuildContext context){
return MaterialApp(
home: MyHome(),
),
}
}
class MyHome extends StatelessWidget{
/// This is home page, it's have Scaffold so the page will using, material guidelines. 
@override
Widget build(BuildContext context){
return Scaffold(
body:Container(
color: Colors.red,
width: 20,
height: 20,
),
);
}
}