本文由 简悦SimpRead 转码,原文地址 rlesovyi.medium.com
学习如何在任何Widget的顶部创建一个自定义的overlay,并使用帮助器。
在以前的文章中,我们讨论了如何创建没有孩子的叶子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;
}
}
firstChild和childAfter是由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);
}
}
而这里是结果。
你可以在我的GitHub上找到实现。 github.com/MatrixDev/F…
希望你喜欢它!
在下一篇文章中,我将展示如何在没有辅助工具的情况下手动实现同样的结果。
- 第1部分 - EllipsizedText
- Part 2.a - ChildSize (with helpers)
- Part 2.b - ChildSize (no helpers)
- Part 3.b - SimpleOverlay (no helpers)