本文由 简悦SimpRead 转码,原文地址 medium.com
学习如何从头开始编写带有多个孩子的自定义Widgets,不需要任何帮助。
按照承诺,现在我将展示如何编写没有任何助手的SimpleOverlay小工具。我将省略一些在以前的文章中已经描述过的模板代码。
在实现可能包含多个孩子的Widget时,我们还需要知道一些事情。
首先,我们需要了解的是槽的概念。插槽基本上只是子Widget在我们Widget里面的一个位置。它与x和y像素坐标无关,而是与逻辑位置有关。例如,在工具条的情况下,它可以代表导航图标、标志、标题或动作(命名为插槽)。但对于柱子来说,它将是每个子元素的索引。
子元素也可以从一个插槽移动到另一个插槽,而不需要重新创造。例如,当我们为我们的子元素指定GlobalKey,然后在构建方法中改变其Slot。或者当我们在我们的元素中调用updateChid并为同一个元素传递不同的Slot时。
还有一件事是调用子方法的顺序。
- 布局 - 顺序并不重要,除非一个子元素的大小取决于另一个子元素的大小。
- 画图 -- 当子元素相互重叠时,从后往前的顺序,否则顺序并不重要。
- HitTest--当孩子们相互重叠时,从前到后的顺序,否则顺序其实并不重要。
第一个区别是,我们的Widget将不再扩展MultiChildRenderObjectWidget,而是切换到更简单的RenderObjectWidget。
class SimpleOverlay extends RenderObjectWidget {
final Widget child;
final Widget overlay;
const SimpleOverlay({
required this.child,
required this.overlay,
});
@override
RenderObjectElement createElement() {
return SimpleOverlayElement(this);
}
@override
RenderObject createRenderObject(BuildContext context) {
return SimpleOverlayRenderObject();
}
}
现在我们需要为我们的元素实际添加儿童管理逻辑。
class SimpleOverlayElement extends RenderObjectElement {
Element? _child;
Element? _overlay;
// ...
@override
void mount(Element? parent, newSlot) {
super.mount(parent, newSlot);
_child = inflateWidget(widget.child, true);
_overlay = inflateWidget(widget.overlay, false);
}
@override
void update(SimpleOverlay newWidget) {
super.update(newWidget);
_child = updateChild(_child, newWidget.child, true);
_overlay = updateChild(_overlay, newWidget.overlay, false);
}
@override
void unmount() {
super.unmount();
_child = null;
_overlay = null;
}
}
这里与之前的 "无助手 "例子的不同之处在于槽的使用。在SimpleOverlay的例子中,我们只有两个Slot - child和overlay。我选择使用布尔值作为槽,因为我们只有两个可能的值--真的孩子和假的覆盖。
同样,为了明确起见,Slot可以是任何你想要的东西,但它们应该是一致的。
还有一件事要补充--就是把子代RenderObjects的生命周期方法传递给我们自己的。
class SimpleOverlayElement extends RenderObjectElement {
// ...
@override
void insertRenderObjectChild(RenderBox child, bool slot) {
renderObject.insertRenderObjectChild(child, slot);
}
@override
void moveRenderObjectChild(RenderBox child, bool oldSlot, bool newSlot) {
renderObject.moveRenderObjectChild(child, oldSlot, newSlot);
}
@override
void removeRenderObjectChild(RenderBox child, bool slot) {
renderObject.removeRenderObjectChild(child, slot);
}
这里唯一的新东西是出现了moveRenderObjectChild方法。正如我之前提到的,当一个孩子从一个槽(oldSlot)移动到一个新的槽(newSlot)时,这个方法将被调用。
我们的元素就这样了。没有什么真正重要的,大部分的代码都是一样的。现在让我们实现我们的RenderObject。首先,我们需要处理来自我们元素的插入、移动和删除调用。
class SimpleOverlayRenderObject extends RenderBox {
//...
RenderBox? _child;
RenderBox? _overlay;
void insertRenderObjectChild(RenderBox child, bool slot) {
if (slot) {
_child = child;
} else {
_overlay = child;
}
adoptChild(child);
}
void moveRenderObjectChild(RenderBox child, bool oldSlot, bool newSlot) {
if (oldSlot) {
_child = null;
} else {
_overlay = null;
}
if (newSlot) {
_child = child;
} else {
_overlay = child;
}
}
void removeRenderObjectChild(RenderBox child, bool slot) {
if (slot) {
_child = null;
} else {
_overlay = null;
}
dropChild(child);
}
}
我们在这里收到的Slot,与我们之前在Element中传递给inflateWidget和updateChild的值相同。感谢这个Slot,我们知道哪个RenderBox应该去哪里。还要注意moveRenderObjectChild方法--我们不需要调用adoptChild或dropChild,因为child实际上是重复使用的,而且已经被连接了。
最后一件事是新的--绘画和点击测试。
class SimpleOverlayRenderObject extends RenderBox {
//...
@override
void paint(PaintingContext context, Offset offset) {
if (_child != null) {
context.paintChild(_child!, offset);
}
if (_overlay != null) {
context.paintChild(_overlay!, offset);
}
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
if (_overlay?.hitTest(result, position: position) == true) {
return true;
}
if (_child?.hitTest(result, position: position) == true) {
return true;
}
return false;
}
}
请注意我们呼出子节点的顺序。我们把overlay画在最后,因为我们想让它在我们的孩子上方可见,但打测试使用的是相反的顺序--overlay应该优先考虑。
下面是结果。
你可以在我的GitHub上找到实现。 github.com/MatrixDev/F…
希望你喜欢它!
- Part 1 - EllipsizedText
- Part 2.a - ChildSize (with helpers)
- Part 2.b - ChildSize (no helpers)
- Part 3.a - SimpleOverlay (with helpers)