[Flutter翻译]在Flutter中编写自定义Widget(第3.a部分)--SimpleOverlay(带帮助)。

88 阅读3分钟

本文由 简悦SimpRead 转码,原文地址 rlesovyi.medium.com

学习如何在任何Widget的顶部创建一个自定义的overlay,并使用帮助器。

image.png

在以前的文章中,我们讨论了如何创建没有孩子的叶子Widget和只有一个孩子的Widget。现在是时候创建更复杂的有多个孩子的Widget了。

今天我将描述如何创建一个SimpleOverlay Widget。它所做的唯一事情就是放置一个Widget,并在保持第一个Widget的大小的情况下将其覆盖在另一个Widget上。这是一个简单的概念,但我在我的项目中经常使用它。

嗯......我们不能用Stack来做这个吗?如果一个孩子的尺寸是预先知道的--可以。但除此之外,它不允许在子代之间有任何依赖关系。

更新:你实际上可以使用 "Stack "来做这个,使用 "Positioned.fill(overlay)",同时指定 "Stack.fit "为 "passthrough"。

首先,我们需要创建我们自己的Widget。

class SimpleOverlay extends MultiChildRenderObjectWidget {
  SimpleOverlay({
    required Widget child,
    required Widget overlay,
  }) : super(children: [child, overlay]);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderSimpleOverlay();
  }
}

这次我们不使用SingleChildRenderObjectWidget,而是扩展MultiChildRenderObjectWidget,因为我们有多个孩子。这个基类要求我们通过构造函数以列表的形式传递所有的孩子。这里的顺序很重要--绘画是从第一个孩子到最后一个孩子进行的,而点击测试则使用相反的顺序。

和SingleChildRenderObjectWidget一样,我们不需要实现我们自己的Element,所有的东西都是为我们处理的。

当使用MultiChildRenderObjectWidget时,我们需要声明一个额外的类 - ContainerBoxParentData。父数据允许我们直接在每个孩子里面存储任意的数据(类似于Android中的LayoutParams)。MultiChildRenderObjectWidget使用ParentData来存储对上一个和下一个孩子的引用,从而形成一个双链表数据结构来存储孩子。

class _SimpleOverlayChild extends ContainerBoxParentData<RenderBox>
    with ContainerParentDataMixin<RenderBox> {}

这就是全部,相当简单。你可以在ContainerParentDataMixin中找到关于链接列表引用的信息。

mixin ContainerParentDataMixin<ChildType extends RenderObject> on ParentData {
  /// The previous sibling in the parent's child list.
  ChildType? previousSibling;
  /// The next sibling in the parent's child list.
  ChildType? nextSibling;
}

下一件事是创建我们的RenderObject。

class RenderSimpleOverlay extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, _SimpleOverlayChild>,
        RenderBoxContainerDefaultsMixin<RenderBox, _SimpleOverlayChild> {
  // ...
}

我们必须混合ContainerRenderObjectMixin,以便在MultiChildRenderObjectWidget中使用我们的RenderObject。这个混合器包含了添加、附加、移动、移除、分离和迭代子RenderObjects的逻辑(换句话说,大量的强制性模板代码)。RenderBoxContainerDefaultsMixin默认情况下什么都不做,而是包含了一些帮助程序,以便在绘画或点击测试时使用。

还记得ParentData吗?好吧,现在我们需要为每个孩子实际设置它。

class RenderSimpleOverlay {
  // ...

  @override
  void setupParentData(covariant RenderObject child) {
    child.parentData = _SimpleOverlayChild();
  }
}

在这之后是我们通常的布局逻辑。

class RenderSimpleOverlay {
  // ...

  @override
  void performLayout() {
    var childConstraints = constraints;

    final child = firstChild;
    if (child != null) {
      child.layout(constraints, parentUsesSize: true);
      childConstraints = BoxConstraints.tight(child.size);
    }

    final overlay = (child == null) ? null : childAfter(child);
    if (overlay != null) {
      overlay.layout(childConstraints, parentUsesSize: true);
    }

    size = child?.size ?? overlay?.size ?? constraints.smallest;
  }
}

firstChildchildAfter是由ContainerRenderObjectMixin提供。如果你记得,第一个孩子是我们的主孩子,第二个孩子是我们的覆盖物。我们使用 "childAfter "而不是 "lastChild",因为 "firstChild "和 "lastChild "在只有一个孩子的情况下会返回同一个对象(这将导致一个错误)。

我们需要实现的最后一件事是绘画和点击测试。这时RenderBoxContainerDefaultsMixin就来帮忙了。

class RenderSimpleOverlay {
  // ...

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    return defaultHitTestChildren(result, position: position);
  }
}

而这里是结果。

1.gif

你可以在我的GitHub上找到实现。 github.com/MatrixDev/F…

希望你喜欢它!

在下一篇文章中,我将展示如何在没有辅助工具的情况下手动实现同样的结果。


www.deepl.com 翻译