有人可以解释一下Flutter中的Builder类是什么吗?

90

文档非常混乱和含糊。以下是它的说明:

Builder类

一个调用闭包以获取其子widget的形式化小部件。

以下是我的问题:

  1. 他们所说的“形式化”是什么意思?
  2. 他们所说的“闭包”是什么意思?
  3. 这个类的目的究竟是什么?

请添加您所参考的文档链接。 - Günter Zöchbauer
1
嗨,Gunter。这是链接:https://docs.flutter.io/flutter/widgets/Builder-class.html - Walter M
“platonic”这一部分还没有答案吗? - Ger
2
我找到了它 - (https://gitter.im/flutter/flutter/archives/2017/05/02) 一个平凡的小部件是“那种东西中最简单的可能的事物”。 - Ger
3个回答

222
经过长时间在互联网上进行的广泛研究后,我收集了一些小片段并将它们组合起来,以清晰明确的方式解释了建造者类(Builder Class)的作用。
术语:根据官方Flutter文档,建造者类定义为:“用于调用闭包以获取其子窗口小部件的平凡窗口小部件。”平凡的意思是该类别中最简单的东西。术语闭包只是lambda函数的另一个名称。
目的:这将是一个冗长的解释,请耐心等待:在Flutter框架中,每个窗口小部件都有一个build方法,该方法接受BuildContext参数。我们必须记住,框架会自动将context对象传递给窗口小部件的build函数。由于框架自动处理了这个问题,因此没有任何理由使任何窗口小部件具有除build之外需要接受context参数的构造函数或函数。因此,如果您要尝试将特定的context对象传递给子节点,则无法这样做。您无法调用build()并手动传递自己的context对象。我的意思是,您可以这样做,但是您将调用两次生成功能:1. 手动调用。2. 框架的自动调用。那么,如何传递特定的context对象?这就是建造者类的作用。建造者类的目的只是构建并返回子窗口小部件。那与其他任何窗口小部件有何不同呢?啊哈!建造者类允许您向其子项传递特定的context对象。建造者类基本上是您自己设置的build函数。我为什么需要传递特定的context对象?让我们看一个例子:假设我们想将新的SnackBar窗口小部件添加到其新的Scaffold父窗口小部件中,该父窗口小部件正在被返回:
 @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: new Container(),
        /// Scaffold doesn't exist in this context here
        /// because the context thats passed into 'build'
        /// refers to the Widget 'above' this one in the tree,
        /// and the Scaffold doesn't exist above this exact build method
        ///
        /// This will throw an error:
        /// 'Scaffold.of() called with a context that does not contain a Scaffold.'
        floatingActionButton: new FloatingActionButton(onPressed: () {
          Scaffold.of(context).showSnackBar(
                new SnackBar(
                  content: new Text('SnackBar'),
                ),
              );
        }));
  }

上述代码不起作用。因为Scaffold.of(context)函数找不到Scaffold,原因如下:

  1. Scaffold小部件尚未创建。
  2. 传递给构建函数的context对象是指父小部件,而不是Scaffold小部件。

那么,我们如何使子SnackBar小部件可以访问父Scaffold小部件?我们使用Builder类来传递Scaffold小部件的上下文:

 @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(),
      /// Builders let you pass context
      /// from your *current* build method
      /// Directly to children returned in this build method
      ///
      /// The 'builder' property accepts a callback
      /// which can be treated exactly as a 'build' method on any
      /// widget
      floatingActionButton: new Builder(builder: (BuildContext context) {
        return new FloatingActionButton(onPressed: () {
          Scaffold.of(context).showSnackBar(
                new SnackBar(
                  backgroundColor: Colors.blue,
                  content: new Text('SnackBar'),
                ),
              );
        });
      }),
    );
  }

请记住,Builder类的构造函数:

Builder({Key key, @required WidgetBuilder builder})

通过将其构建委托给通过构造函数传递的回调函数来创建小部件。

因此,在以下代码中:

new Builder(builder: (BuildContext context){ ... });

我们提供了一个闭包,其中:

  1. 包含一个BuildContext context参数
  2. 基于传递的context构建并返回子widget(s)

基本上,你提供了自己的构建函数。这个闭包中的BuildContext context参数是Scaffold的context!太棒了!

就是这样。Flutter文档根本没有提供很详细的解释。我觉得我要比解码Flutter文档更容易理解古代象形文字。

总结:对于那些仍然难以理解这个概念的人,让我用更简明的方式来解释。Builder函数只是允许你获得并使用包含Builder widget的父widget(即Scaffold)的context对象。在上面的示例中,它是new Scaffold() widget。记住,唯一可用的context对象是父widget(即Scaffold)的,因为当前widget (Scaffold)还没有被创建。我希望这能帮助那些还在纠结的人。


1
嗨,我认为这句话可能不正确:“传递给构建函数的上下文对象是指父部件,而不是Scaffold部件。” 我认为上下文应该指当前部件,而不是父部件。 - sgon00
1
简而言之,Builder 用于创建类似于“内联”小部件的东西,您可能不想为此创建单独的类。 - Thành Chung Bùi
20
谢谢你进行研究并发布如此清晰的解释。这非常有帮助!所有有关Builder的Flutter文档都是“一个调用闭包以获取其子部件的理想化小部件。”几乎没有用! - Ray Li
在阅读完这篇文章后,我才意识到“柏拉图式”的这个词有多可怕。 - John Wang
3
我认为这里选择的词可能会误导。我不会说你使用它来“传递特定上下文”,因为这可能被理解为“任何你想要的上下文”,而实际上它是由框架控制的,并且始终比周围的上下文深一个上下文。 - Quacke
显示剩余4条评论

28

它基本上将构建小部件的函数转换为小部件。

当您需要传递小部件但只有返回小部件的函数时,可以使用 Builder 小部件。

bool bar;

Widget createFooOrBarWidget() {
  if(bar) {
    return BarWidget();
  } 
  return FooWidget();
}

Widget build(BuildContext context) =>
  Container(child: Builder((context) => createFooOrBarWidget()));

你也可以使用

Widget build(BuildContext context) =>
  Container(child: createFooOrBarWidget());

但前者会延迟创建 Foo 或 Bar 小部件,直到实际调用 build 方法。


嗯...我想我懂了。你能为我确认一个事实吗:每个小部件都有一个构建方法吗? - Walter M
是的,没有build就不能有小部件。当Flutter重新构建视图时,会对每个小部件调用build方法。 - Günter Zöchbauer
2
好的。确认无误后,我将根据您提供的信息和我花费23个小时寻找清晰解释Builder类的答案来回答我的问题。请给我几分钟时间发布我的答案并查看是否正确。 - Walter M
抱歉耽搁了,Gunter。我已经发布了我的答案。你能帮我再检查一遍吗? - Walter M
请您能否详细解释一下“但前者会延迟创建 Foo 或 Bar 小部件,直到实际调用 build 方法。”?我很喜欢这两个代码片段,想要了解它们的区别。 - aytunch
其实我自己也看不懂。我在回答时刚开始接触Flutter。 - Günter Zöchbauer

11

简单定义

如其名,Builder Widget是用来创建一个带有“新上下文”的子Widget的。

技术定义

这个Builder Widget具有一个builder属性,该属性接受WidgetBuilder typedef(WidgetBuilder typedef是一个函数签名,用于创建(返回)一个带有新上下文的Widget)

如果您想了解WidgetBuilder typedef,请使用此链接➡https://api.flutter.dev/flutter/widgets/WidgetBuilder.html

用法:


1. 当Scaffold widgetScaffold.of方法在同一构建方法中时。

[ 这时scaffold.of方法找不到最近的Scaffold widget,因为两者在相同的上下文中,通过在build方法内创建新上下文,可以解决这个问题,这就是为什么我们使用Builder Widget创建带有新BuildContext的Widget。]

下面的代码展示了使用Builder Widget的真实用例,当您想要在同一构建方法中使用Scaffold.ofScaffold widget时。

(仔细查看这些注释-它将帮助您理解上下文)

Widget build(BuildContext context) {                       // context - 1
  return Scaffold(
    appBar: AppBar(
      title: Text('Demo')
    ),
    body: Builder(
      // Create an inner BuildContext so that the onPressed methods
      // can refer to the Scaffold with Scaffold.of().
      builder: (BuildContext context) {                    // context - 2
        return Center(
          child: RaisedButton(
            child: Text('SHOW A SNACKBAR'),
            onPressed: () {
              Scaffold.of(context).showSnackBar(SnackBar(  // here context is (context- 2)
                content: Text('Have a snack!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

2. 当在同一个构建方法中使用Theme.of方法和Theme小部件时。

[这里的目的与上面1相同]

以下代码展示了Builder小部件的实际用法,当您想要在同一个构建方法中使用Theme.of方法和Theme小部件时。

@override
Widget build(BuildContext context) {
  return MaterialApp(
    theme: ThemeData.light(),
    body: Builder(
      // Create an inner BuildContext so that we can refer to
      // the Theme with Theme.of().
      builder: (BuildContext context) {
        return Center(
          child: Text(
            'Example',
            style: Theme.of(context).textTheme.title,
          ),
        );
      },
    ),
  );
}

额外加分项

我们可以在许多实例中看到 buider property (WidgetBuilder typedef)

下面的代码部分展示了,MaterialPageRoute 如何使用 builder property 来获取该路由的小部件(widget)。


Navigator.push(context, MaterialPageRoute<void>(
  builder: (BuildContext context) {                 //here
    return Scaffold(
      appBar: AppBar(title: Text('My Page')),
      body: Center(
        child: FlatButton(
          child: Text('POP'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  },
));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接