Flutter:了解 Stateless/Statefull 小部件如何布局其子部件?
Flutter: Understanding how does a Stateless/Statefull widget layout its child?
TL;DR
如果没有关于小部件的文档,我怎么知道小部件的布局规则(它会从其父级请求什么大小以及它会向其子级传递什么约束)?
问题详情
我有这个非常基本的应用程序
void main() {
runApp(
Container(
color: Colors.red,
width: 20,
height: 20,
),
);
}
我原以为 Container
的宽度和高度为 20,但我得到的 Container
填满了整个屏幕。
阅读 this article on flutter.dev 了解约束,在其 最后一部分称为“学习特定小部件的布局规则”
“,他们提到如何通过找到 createRenderObject
方法然后找到 performLayout
方法来做到这一点。
然而,此 createRenderObject
方法仅适用于 RenderObjectWidget
的子类。例如,浏览 Transform
小部件的代码,我发现 createRenderObject
那个 returns 一个 RenderTransform
,它扩展了 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
。我无法通过浏览它的代码找到方法 performLayout
和 createRenderObject
,我只能找到 createElement
,但我正在寻找关联的渲染树中的 RenderObject
使用容器而不是元素。
问题
所以问题是如何找到与无状态 widget/stateful 小部件关联的渲染对象,以便了解此小部件将为其子项提供的布局规则,并在这种情况下自行遵循这些规则?
嗯,不要太复杂。这是我可以向您解释的最简单的事情,让我们将应用程序分成 4 个不同的层。
- 应用程序 -> 您可以使用
MaterialApp
创建一个 material 应用程序
- Widget Pages -> 您可以选择使用
Stateless
或StatefulWidget
,这取决于您的需要。如果你需要动态的,有很多变化的状态,你可以使用 StatefulWidget
或者你可以用 StatelessWidget
. 创建一个静态页面
- 脚手架->Widget页面必须return一个脚手架才能组成material个页面,这里可以使用
Scaffold
,可以添加appBar,fab,body,bottomNavigationBar等
- Widgets -> 这里的widgets可以是任何东西,它可以是你的scaffold appBar的appBar,或者ListView,GridView或者Custom widget。
所以你的代码必须看起来像这样
/// 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,
),
);
}
}
你说的有道理。我会说我的文章在这方面不够精确。
小部件不需要创建 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!;
}
如您所见,它是根据其他小部件定义的。这些小部件也可以根据其他小部件等进行定义。但在某些时候,您将到达创建 RenderObject
s.
的小部件
关于 Container
不是 20x20 的原因,正如文章所解释的那样,尺寸是由 parent 设置的。所以 Container
s 大小由 Container
的 parent 设置,在本例中是 screen。屏幕总是强制其 child 占用所有可用的 space,在这种情况下忽略了 Container
对 20x20 的要求。这里的修复是给 Container
另一个 parent。一种允许 Container
选择自己的大小。例如,Center
和 Align
都会让这种情况发生,这就是为什么您可以通过以下方式解决问题:
void main() {
runApp(
Center(
child: Container(
color: Colors.red,
width: 20,
height: 20,
),),);
}
至于为什么屏幕会强制其 child 占用所有可用空间 space:这正是 Flutter 创作者决定的方式。如果你深入研究 Flutter 的代码,你会在那里找到它。但您最好记住这个事实。
希望对您有所帮助!
TL;DR
如果没有关于小部件的文档,我怎么知道小部件的布局规则(它会从其父级请求什么大小以及它会向其子级传递什么约束)?
问题详情
我有这个非常基本的应用程序
void main() {
runApp(
Container(
color: Colors.red,
width: 20,
height: 20,
),
);
}
我原以为 Container
的宽度和高度为 20,但我得到的 Container
填满了整个屏幕。
阅读 this article on flutter.dev 了解约束,在其 最后一部分称为“学习特定小部件的布局规则”
“,他们提到如何通过找到 createRenderObject
方法然后找到 performLayout
方法来做到这一点。
然而,此 createRenderObject
方法仅适用于 RenderObjectWidget
的子类。例如,浏览 Transform
小部件的代码,我发现 createRenderObject
那个 returns 一个 RenderTransform
,它扩展了 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
。我无法通过浏览它的代码找到方法 performLayout
和 createRenderObject
,我只能找到 createElement
,但我正在寻找关联的渲染树中的 RenderObject
使用容器而不是元素。
问题
所以问题是如何找到与无状态 widget/stateful 小部件关联的渲染对象,以便了解此小部件将为其子项提供的布局规则,并在这种情况下自行遵循这些规则?
嗯,不要太复杂。这是我可以向您解释的最简单的事情,让我们将应用程序分成 4 个不同的层。
- 应用程序 -> 您可以使用
MaterialApp
创建一个 material 应用程序 - Widget Pages -> 您可以选择使用
Stateless
或StatefulWidget
,这取决于您的需要。如果你需要动态的,有很多变化的状态,你可以使用StatefulWidget
或者你可以用StatelessWidget
. 创建一个静态页面
- 脚手架->Widget页面必须return一个脚手架才能组成material个页面,这里可以使用
Scaffold
,可以添加appBar,fab,body,bottomNavigationBar等 - Widgets -> 这里的widgets可以是任何东西,它可以是你的scaffold appBar的appBar,或者ListView,GridView或者Custom widget。
所以你的代码必须看起来像这样
/// 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,
),
);
}
}
你说的有道理。我会说我的文章在这方面不够精确。
小部件不需要创建 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!;
}
如您所见,它是根据其他小部件定义的。这些小部件也可以根据其他小部件等进行定义。但在某些时候,您将到达创建 RenderObject
s.
关于 Container
不是 20x20 的原因,正如文章所解释的那样,尺寸是由 parent 设置的。所以 Container
s 大小由 Container
的 parent 设置,在本例中是 screen。屏幕总是强制其 child 占用所有可用的 space,在这种情况下忽略了 Container
对 20x20 的要求。这里的修复是给 Container
另一个 parent。一种允许 Container
选择自己的大小。例如,Center
和 Align
都会让这种情况发生,这就是为什么您可以通过以下方式解决问题:
void main() {
runApp(
Center(
child: Container(
color: Colors.red,
width: 20,
height: 20,
),),);
}
至于为什么屏幕会强制其 child 占用所有可用空间 space:这正是 Flutter 创作者决定的方式。如果你深入研究 Flutter 的代码,你会在那里找到它。但您最好记住这个事实。
希望对您有所帮助!