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

Posted

技术标签:

【中文标题】Flutter:了解无状态/有状态小部件如何布局其子级?【英文标题】:Flutter: Understanding how does a Stateless/Statefull widget layout its child? 【发布时间】:2021-12-09 19:30:36 【问题描述】:

TL;DR

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

问题详情

我有这个非常基本的应用程序

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

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

阅读thisflutter.dev 上关于理解约束的文章,在其last part called "Learning the layout rules for specific widgets " 中,他们提到了如何通过找到createRenderObject 方法然后找到performLayout 方法来做到这一点。

但是,此createRenderObject 方法仅适用于RenderObjectWidget 的子类。例如,浏览Transform小部件的代码,我发现createRenderObject返回一个RenderTransform,扩展RenderProxyBox,最终实现performLayout为:

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

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

但在上面Container的情况下,是直接扩展StatelessWidget。我无法通过其代码导航找到performLayoutcreateRenderObject 方法,我只能找到createElement,但我在与容器关联的渲染树中寻找RenderObject,而不是元素。

问题

所以问题是如何找到与无状态小部件/有状态小部件相关联的渲染对象,以便了解该小部件将为其子级提供的布局规则并在这种情况下自行遵循它们?

【问题讨论】:

【参考方案1】:

你说得有道理。我想说我的文章在这方面不够精确。

小部件不需要创建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 的原因,正如文章所解释的那样,尺寸是由父母设置的。因此Containers 的大小由Container 的父级设置,在本例中为屏幕。并且屏幕总是强制它的子元素占据所有可用空间,在这种情况下忽略了Container 想要成为 20x20 的愿望。这里的修复是给Container另一个父母。一种允许Container 选择自己的大小。例如,CenterAlign 都会让这种情况发生,这就是为什么您可以通过以下方式解决问题:

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

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

希望对你有帮助!

【讨论】:

感谢您花时间回答这个问题,我只是在学习这个布局和渲染的东西,它的复杂性让我错过了首先看构建方法:) 谢谢!【参考方案2】:

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

    应用程序 -> 可以使用MaterialApp 创建材质应用程序 Widget Pages -> 您可以选择使用StatelessStatefulWidget,这取决于您的需要。如果你需要动态的,有很多变化的状态,你可以使用StatefulWidget,或者你可以使用StatelessWidget创建一个静态页面。 Scaffold -> Widget pages必须返回一个scaffold才能形成一个material pages,这里可以使用Scaffold,可以添加appBar、fab、body、bottomNavigationBar等。 Widgets -> 这里的 widgets 可以是任何东西,它可以是你的脚手架 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,
       ),
    );
  

【讨论】:

谢谢,但我的问题是关于如何知道任何小部件的布局规则,但这并不能回答我的问题。 哥们,你错过了这里的重点Scaffold 是让你的小部件按照你的期望出现的那个,没有脚手架你的小部件将无法正确显示 这只是一个问题的演示,我知道脚手架,你读过 TL;DR 吗?请阅读以了解我的问题 好吧,我不知道你想达到什么目标。祝你好运

以上是关于Flutter:了解无状态/有状态小部件如何布局其子级?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 状态管理(BloC):无状态与有状态小部件

Flutter 中的有状态和无状态小部件之间的关系是啥?

有状态与无状态的区别如何影响小部件何时重建?

在 Flutter 中使用 BLoC - 在有状态小部件与无状态小部件中的使用

从无状态小部件调用有状态小部件的 setState

Flutter:继承自抽象的无状态小部件