如何在 Flutter 中使用 CustomMultiChildLayout & CustomSingleChildLayout

Wal*_*r M 39 dart flutter flutter-layout

有使用经验CustomSingleChildLayoutCustomMultiChildLayout类的人能不能详细解释一下(用例子)如何使用它们。

我是 Flutter 的新手,正在尝试了解如何使用这些。但是,文档很糟糕,而且不清楚。我试图在互联网上搜索示例,但没有任何其他文档。

如果您能提供帮助,我将永远感激不尽。

谢谢!

cre*_*not 91

首先,我想说我很高兴能帮助你解决这个问题,因为我能理解你的挣扎 - 自己解决它也有好处(文档很棒)。

CustomSingleChildLayout在我CustomMultiChildLayout向你解释之后,什么将是显而易见的。

CustomMultiChildLayout

这个小部件的要点是允许您在单个函数中布置您传递给该小部件的子项,即它们的位置和大小可以相互依赖,这是使用例如预构建的小部件无法实现的Stack

CustomMultiChildLayout(
  children: [
    // Widgets you want to layout in a customized manner
  ],
)
Run Code Online (Sandbox Code Playgroud)

现在,在开始布置孩子之前,您还需要采取两个步骤

  1. 您传递给的每个孩子都children需要是 aLayoutId并且您将您实际想要作为孩子显示的小部件传递给那个LayoutId。该id会唯一识别您的小工具,使他们可以访问铺设出来的时候:
CustomMultiChildLayout(
  children: [
    LayoutId(
      id: 1, // The id can be anything, i.e. any Object, also an enum value.
      child: Text('Widget one'), // This is the widget you actually want to show.
    ),
    LayoutId(
      id: 2, // You will need to refer to that id when laying out your children.
      child: Text('Widget two'),
    ),
  ],
)
Run Code Online (Sandbox Code Playgroud)
  1. 您需要创建一个MultiChildLayoutDelegate处理布局部分的子类。这里的文档似乎非常详细。
class YourLayoutDelegate extends MultiChildLayoutDelegate {
  // You can pass any parameters to this class because you will instantiate your delegate
  // in the build function where you place your CustomMultiChildLayout.
  // I will use an Offset for this simple example.

  YourLayoutDelegate({this.position});

  final Offset position;
}
Run Code Online (Sandbox Code Playgroud)

现在,所有设置都已完成,您可以开始实施实际布局。您可以使用三种方法:

  • hasChild,它可以让您检查特定的id(还记得LayoutId吗?)是否被传递给children,即是否存在该 id 的孩子。

  • layoutChild,你需要为每个id,每个孩子调用它,只提供一次,它会给你Size那个孩子的。

  • positionChild,它允许您将位置更改为Offset(0, 0)您指定的任何偏移量。

我觉得这个概念现在应该很清楚了,这就是为什么我将说明如何为示例实现委托CustomMultiChildLayout

class YourLayoutDelegate extends MultiChildLayoutDelegate {
  YourLayoutDelegate({this.position});

  final Offset position;

  @override
  void performLayout(Size size) {
    // `size` is the size of the `CustomMultiChildLayout` itself.

    Size leadingSize = Size.zero; // If there is no widget with id `1`, the size will remain at zero.
    // Remember that `1` here can be any **id** - you specify them using LayoutId.
    if (hasChild(1)) {
      leadingSize = layoutChild(
        1, // The id once again.
        BoxConstraints.loose(size), // This just says that the child cannot be bigger than the whole layout.
      );
      // No need to position this child if we want to have it at Offset(0, 0).
    }

    if (hasChild(2)) {
      final secondSize = layoutChild(
        2,
        BoxConstraints(
          // This is exactly the same as above, but this can be anything you specify.
          // BoxConstraints.loose is a shortcut to this.
          maxWidth: size.width,
          maxHeight: size.height,
        ),
      );

      positionChild(
        2,
        Offset(
          leadingSize.width, // This will place child 2 to the right of child 1.
          size.height / 2 - secondSize.height / 2, // Centers the second child vertically.
        ),
      );
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

另外两个示例是文档中的示例(检查准备步骤 2)和我一段时间为包编写的真实示例feature_discoveryMultiChildLayoutDelegateimplementationCustomMultiChildLayoutin the buildmethod

最后一步是覆盖shouldRelayout方法,它performLayout通过与旧委托进行比较(可选地,您也可以覆盖getSize)并将委托添加到您的CustomMultiChildLayout

class YourLayoutDelegate extends MultiChildLayoutDelegate {
  YourLayoutDelegate({this.position});

  final Offset position;

  @override
  void performLayout(Size size) {
    // ... (layout code from above)
  }

  @override
  bool shouldRelayout(YourLayoutDelegate oldDelegate) {
    return oldDelegate.position != position;
  }
}
Run Code Online (Sandbox Code Playgroud)
CustomMultiChildLayout(
  delegate: YourLayoutDelegate(position: Offset.zero),
  children: [
    // ... (your children wrapped in LayoutId's)
  ],
)
Run Code Online (Sandbox Code Playgroud)

注意事项

  • 我在本例中使用1and2作为id,但enum如果您有特定的 id ,则使用 an可能是处理 id 的最佳方式。

  • 如果您想为布局过程设置动画或根据一般的可听对象触发它,您可以传递一个Listenableto super(eg super(relayout: animation))。

CustomSingleChildLayout

文档很好地解释了我上面描述的内容,在这里您还将明白为什么我说这CustomSingleChildLayout在理解CustomMultiChildLayout工作原理后会非常明显:

当多个小部件的大小和位置之间存在复杂关系时,CustomMultiChildLayout是合适的。要控制单个孩子的布局,CustomSingleChildLayout更合适。

这也意味着 usingCustomSingleChildLayout遵循我上面描述的相同原则,但没有任何 id,因为只有一个孩子。
您需要使用 aSingleChildLayoutDelegate代替,它具有不同的实现布局的方法(它们都有默认行为,因此从技术上讲,它们都可以选择覆盖):

其他一切都完全相同(请记住,您不需要LayoutId并且只有一个孩子而不是children)。


MultiChildRenderObjectWidget

这就是CustomMultiChildLayout建立在上面的。
使用它需要对 Flutter 有更深入的了解,也有点复杂,但如果你想要更多的自定义,它是更好的选择,因为它的级别更低。这有一个主要优势CustomMultiChildLayout(通常,有更多的控制):

CustomMultiChildLayout 无法根据其子项调整自身大小有关推理的更好文档,请参阅问题)。

MultiChildRenderObjectWidget由于明显的原因,我不会在这里解释如何使用,但是如果您有兴趣,可以查看在 2020 年 1 月 20 日之后提交的 Flutter Clock 挑战,我在其中MultiChildRenderObjectWidget广泛使用- 您还可以阅读有关此的文章,这应该解释一下它是如何工作的。

现在你可以记住这MultiChildRenderObjectWidget是什么使成为CustomMultiChildLayout可能,直接使用它会给你一些很好的好处,比如不必使用LayoutId而是能够直接访问RenderObject父数据。

有趣的事实

我用纯文本编写了所有代码(在 StackOverflow 文本字段中),所以如果有错误,请指出它们,我会修复它们。

  • 哇!你是我的救星!这就是我正在谈论的!这就是颤振文档应该是这样的!我无法仅使用普通文档来弄清楚这些类是如何工作的。太感谢了! (4认同)
  • 确实令人惊奇的答案。例如,当您需要将自动调整大小的多个文本小部件分组到“行”的“列”中时,“CustomMultiChildLayout”非常有用。 (3认同)
  • @Otziii 事实上,顺序 *painting* 决定了 z 顺序。对于 CustomMultiChildLayout,绘制顺序与子列表相同。然而,并不能真正保证它会一直以这种方式运行,因为例如,有人可以实现以相反顺序绘制的 RenderObject。 (2认同)