Flutter:如何在渲染以在其父级布局中使用它之前获取叠加层中子窗口小部件的大小?

Flutter: How to get the size of a child widget in an overlay before it is rendered to use it in its parent's layout?

TL;DR

我有一个父项取决于子项的大小,并且该子项位于叠加层中并且在按下父项之前不会呈现,如何在渲染之前获取子项的大小以正确布局父项?

详情

我编辑了 flutter 的 PopupMenuButton 并创建了一个 custom PopupButton,其中包含一个 childcontent 和一个 offsetBuilder.

当按下child时,它可以在Overlay上显示content。内容在叠加层上的位置根据从 offsetBuilder 返回的偏移量确定,如下所示:

             PopupButton(
              child: Container(
                child: const Text('MMMMMMMMMMM\nMMMMMMMMMMM\nMMMMMMMMMMM',
                    style: TextStyle(fontSize: 60), maxLines: 10,),
                decoration:
                    BoxDecoration(border: Border.all(color: Colors.red)),
              ),
              content: popupContent,
              offsetBuilder: (size) {
                // size is the size of the child above
                return Offset((size.width - 500) / 2, 0.0);
              },
            )

上面返回的偏移量从 child 的左上角开始翻译 content,如下所示:

我需要知道 content(蓝色行)的大小,以便将它放在 child(上面的 Text 小部件)上方,就像这里(偏移量是硬编码的):

问题是 content 直到我按下 Text 才真正呈现,所以在上面的代码中使用 GlobalKey 将不起作用,因为当上面的代码执行 conten 仍未实际呈现,全局键返回的 Size 将为零或空值。

此外,使用 this solution 可能会解决问题,但会使小部件渲染错误 1 ​​帧,然后一切都会正确(它正在使用 WidgetsBinding.instance.addPostFrameCallback)。

如何获得 content 的大小以在 offsetBuilder 中使用它?

您可以使用 LayoutBuilder 获取父控件的尺寸数据。这是示例:

Container(
   width: _mainWidth,
   height: _mainHeight,
   color: Colors.blue,
   child: Center(
       child: LayoutBuilder(
           builder: (BuildContext context, BoxConstraints constraints) {
               return Container(
                 width: constraints.maxWidth / 2,
                 height: constraints.maxHeight / 2,
                 color: Colors.yellow,
               );},
           ),
       ),
   ),

约束可以为您提供宽度和高度信息。在上面的示例中,子小部件的大小是其父小部件的一半。

我不知道这是否是最干净和最佳的方法,但我可以使用 CustomSingleChildLayout 小部件解决它。根据文档,此小部件采用一个子项并将其布局委托给某些 SingleChildLayoutDelegate。因此,在这种情况下,我所要做的就是将此 SingleChildLayoutDelegate 发送到弹出按钮的 SizeOffset,然后在委托的 getPositionForChild 方法中进行计算。 然而,这迫使我重构 PopupButton 的代码以在其构造函数中接受一个 ContentBuilder,这是在 PopupButton 内部调用的函数,它将传递按钮 SizeOffset 并获取 CustomSingleChildLayout 小部件。

这是CustomSingleChildLayout class:

    class PopupContent extends StatelessWidget {
  final Size popupButtonSize;
  final Offset popupButtonOffset;

  const PopupContent({
    Key? key,
    required this.popupButtonSize,
    required this.popupButtonOffset,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final moc = MediaQuery.of(context);
    return CustomSingleChildLayout(
      delegate: PopupContentSingleChildLayoutDelegate(
        popupButtonSize: popupButtonSize,
        popupButtonOffset: popupButtonOffset,
        padding: moc.padding,
      ),
      child: ... the blue row,
    );
  }
}

class PopupContentSingleChildLayoutDelegate extends SingleChildLayoutDelegate {
  final Size popupButtonSize;
  final Offset popupButtonOffset;
  final EdgeInsets padding;

  PopupContentSingleChildLayoutDelegate({
    required this.popupButtonSize,
    required this.popupButtonOffset,
    required this.padding,
  });

  @override
  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) {
    return this != oldDelegate;
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    Offset offset = Offset((popupButtonSize.width - childSize.width) / 2, 0);
    offset = popupButtonOffset
        .translate(offset.dx, offset.dy)
        .translate(0, -childSize.height);

    // Find the ideal vertical position.
    double y = offset.dy;

    // Find the ideal horizontal position.
    double x = offset.dx;

  ...some other calculations

    return Offset(x, y);
  }
}

这就是我现在的称呼

PopupButton(
              child: Container(
                decoration:
                    BoxDecoration(border: Border.all(color: Colors.red)),
                child: Text(text,
                    maxLines: 3, style: const TextStyle(fontSize: 60)),
              ),
              contentBuilder: (size, offset) => PopupContent(
                popupButtonOffset: offset,
                popupButtonSize: size,
              ),
            )

现在它可以工作了,但也许是时候对代码进行更多的抽象和清理了。