简体   繁体   English

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

[英]Flutter: Understanding how does a Stateless/Statefull widget layout its child?

TL;DR长话短说

How can I know the layout rules of a widget (what size will it request from its parent and what constraints will it pass to its children) if there is no documentation about it?如果没有关于小部件的文档,我如何知道小部件的布局规则(它会从其父级请求什么大小以及它将向其子级传递什么约束)?

The problem details问题详情

I have this very basic app我有这个非常基本的应用程序

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

I was expecting the Container to have width and height of 20 but I got a Container that filled up the whole screen.我期望Container的宽度和高度为 20,但我得到了一个填满整个屏幕的Container

Reading this article on flutter.dev about understanding constraints, in its last part called "Learning the layout rules for specific widgets " , they mention how to do this by finding the createRenderObject method and then finding the performLayout method.flutter.dev上阅读这篇关于理解约束的文章,在其最后一部分“学习特定小部件的布局规则”中,他们提到如何通过找到createRenderObject方法然后找到performLayout方法来做到这一点。

However this createRenderObject method is only available for subclasses of RenderObjectWidget .但是,此createRenderObject方法仅适用于RenderObjectWidget的子类。 For example, navigating through code of Transform widget, I find createRenderObject that returns a RenderTransform , that extends RenderProxyBox , which finally implements performLayout as:例如,浏览Transform小部件的代码,我发现createRenderObject返回一个RenderTransform ,它扩展了RenderProxyBox ,最终performLayout实现为:

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

I can conclude that Transform widget will finally take the size of its child due to this line size = child.;size;我可以得出结论,由于这条线size = child.;size;Transform小部件最终将采用其子部件的大小。 . .

But in case of Container above, is directly extends StatelessWidget .但在上面的Container的情况下,是直接扩展StatelessWidget I couldn't find by navigating through its code the methods performLayout and createRenderObject , I could only find createElement , but I am looking for the RenderObject in the render tree associated with the Container and not the element.通过浏览它的代码,我找不到方法performLayoutcreateRenderObject ,我只能找到createElement ,但我正在寻找渲染树中与 Container 而不是元素关联的RenderObject

The Question问题

So the question is how to find this render object associated with a stateless widget/stateful widget in order to know the layout rules that this widget will give to its children and will follow them itself in this case?所以问题是如何找到与无状态小部件/有状态小部件相关联的渲染 object,以便了解该小部件将为其子项提供的布局规则,并在这种情况下自行遵循这些规则?

You have a point.你有一定道理。 I'd say my article is imprecise in that regard.我会说我的文章在这方面不够精确。

A widget doesn't need to create a RenderObject .小部件不需要创建RenderObject It can, instead, use a composition of other widgets that create RenderObjects themselves.相反,它可以使用自己创建RenderObjects的其他小部件的组合。

If a widget is a composition of other widgets, then instead of looking at the performLayout you can simply look at that widget's build method to see what it's doing.如果小部件是其他小部件的组合,则无需查看performLayout您只需查看该小部件的build方法即可了解它在做什么。 In the case of a Container , this is its build method:对于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!;
  }

As you can see, it is defined in terms of other widgets.如您所见,它是根据其他小部件定义的。 And these widgets may also be defined in terms of other widgets and so on.并且这些widget也可以根据其他widget等来定义。 But at some point you will reach the widgets that create the RenderObject s.但是在某些时候,您将到达创建RenderObject的小部件。


Regarding the reason why the Container is not 20x20, it's because, as the article explains, sizes are set by parents.关于Container不是 20x20 的原因,这是因为,正如文章所解释的,尺寸是由父母设置的。 So the Container s size is set by the Container 's parent, which in this case is the screen .因此Container的大小由Container的父级设置,在这种情况下是screen And the screen always forces its child to occupy all the available space, in this case ignoring the Container 's desire to be 20x20.并且屏幕总是强制它的孩子占据所有可用空间,在这种情况下忽略了Container想要成为 20x20 的愿望。 The fix here is giving the Container another parent.这里的修复是给Container另一个父级。 One which allows the Container to choose its own size.一种允许Container选择自己的大小。 For example, both Center and Align will let that happen, and that's why you can fix the problem by doing:例如, CenterAlign都会让这种情况发生,这就是您可以通过执行以下操作来解决问题的原因:

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

As to why the screen forces its child to occupy all the available space: That's just the way Flutter creators decided it should be.至于为什么屏幕会强迫它的孩子占据所有可用空间:这正是 Flutter 创作者决定的方式。 If you dig into Flutter's code you will find it there.如果你深入研究 Flutter 的代码,你会在那里找到它。 But it's probably best that you just remember this fact.但最好记住这个事实。

Hope it helps!希望能帮助到你!

You are missing the architecture here, we need to use Scaffold .您在这里缺少架构,我们需要使用Scaffold like喜欢

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

Meh, don't get too complex yet.嗯,不要太复杂了。 here is simplest thing i can explain to you, lets separate an application into 4 different layer.这是我可以向您解释的最简单的事情,让我们将应用程序分成 4 个不同的层。

  1. Application -> You can use MaterialApp to create an material application应用程序 -> 您可以使用MaterialApp创建一个材质应用程序
  2. Widget Pages -> You can choose between using Stateless or StatefulWidget , it depends on your need. Widget Pages -> 您可以选择使用StatelessStatefulWidget ,这取决于您的需要。 If you need dynamic, with aa lot of changing states you can use StatefulWidget or you can create a static page with StatelessWidget .如果你需要动态的,有很多变化的状态,你可以使用StatefulWidget或者你可以使用StatelessWidget创建一个静态页面。
  3. Scaffold -> Widget pages must return a scaffold in order to form an material pages, here you can use Scaffold , you can add appBar, fab, body, bottomNavigationBar & etc. Scaffold -> Widget 页面必须返回一个脚手架才能形成一个材质页面,这里可以使用Scaffold ,可以添加 appBar、fab、body、bottomNavigationBar 等。
  4. Widgets -> Here the widgets can be anything, it's can be an appBar for your scaffold appBar, or ListView, GridView or Custom widget.小部件 -> 小部件可以是任何东西,可以是脚手架 appBar 的 appBar,也可以是 ListView、GridView 或自定义小部件。

So your code must be look like this, kind of所以你的代码必须是这样的

/// 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,
       ),
    );
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM