简体   繁体   中英

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.

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.

However this createRenderObject method is only available for subclasses of RenderObjectWidget . For example, navigating through code of Transform widget, I find createRenderObject that returns a RenderTransform , that extends RenderProxyBox , which finally implements performLayout as:

  @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; .

But in case of Container above, is directly extends 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.

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?

You have a point. I'd say my article is imprecise in that regard.

A widget doesn't need to create a RenderObject . It can, instead, use a composition of other widgets that create RenderObjects themselves.

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. In the case of a Container , this is its build method:

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. But at some point you will reach the widgets that create the RenderObject s.


Regarding the reason why the Container is not 20x20, it's because, as the article explains, sizes are set by parents. So the Container s size is set by the Container 's parent, which in this case is the 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. The fix here is giving the Container another parent. One which allows the Container to choose its own size. For example, both Center and Align will let that happen, and that's why you can fix the problem by doing:

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. If you dig into Flutter's code you will find it there. 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 . 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.

  1. Application -> You can use MaterialApp to create an material application
  2. Widget Pages -> You can choose between using Stateless or StatefulWidget , it depends on your need. If you need dynamic, with aa lot of changing states you can use StatefulWidget or you can create a static page with 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.
  4. Widgets -> Here the widgets can be anything, it's can be an appBar for your scaffold appBar, or ListView, GridView or Custom widget.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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