本文由 简悦SimpRead 转码,原文地址 rlesovyi.medium.com
了解如何管理子RenderObjects以及如何在子Widget改变尺寸时接收回调......。
是时候发表我的第二篇文章了。这一次,它将是一个相当简单的Widget,当它的孩子的尺寸发生变化时通知我们。这个任务非常简单,但它的重点是向你展示如何在RenderObject中管理孩子。
当我刚开始接触Flutter的时候,这是我的问题之一,甚至在Udemy上完成了课程,可惜的是,我没有得到答案。在StackOverflow上,人们建议在Widget上使用GlobalKey,找到它的RenderObject并获取它的大小。
虽然上面的解决方案没有什么问题,但我还是不喜欢它的某些地方。
- 它改变了Widget的元素的销毁方式
- 你需要在声明性的Widget结构中加入指令性代码
- 没有能力实际跟踪Widget的大小,它需要按需提取。
一般来说,我想实现的是以下几点。
return ChildSize(
child: buildChildWidget(),
onChildSizeChanged: (size) => handleNewChildSize(size),
);
当编写一个包含儿童的自定义Widget时,有几件重要的事情我们需要知道。
- 对于每个自定义Widget,我们需要编写它的Element和(有时)RenderObject的实现。
- Element将把它的子Widget膨胀成一个单独的Elements,并在需要时更新/重新创建它们。
- RenderObject有几个重要的角色--孩子们的管理、布局、绘画和点击测试(鼠标指针、触摸事件等)。
让我们开始吧。首先,我们需要声明一个实际的Widget。
class ChildSize extends SingleChildRenderObjectWidget {
final void Function(Size)? onChildSizeChanged;
const ChildSize({
Key? key,
Widget? child,
this.onChildSizeChanged,
}) : super(key: key, child: child);
}
除了SingleChildRenderObjectWidget,这里没有什么新东西。这是Flutter框架中的一个辅助工具,它允许我们编写不超过一个孩子的自定义Widget。这大大简化了我们的代码,因为我们根本不需要写自定义元素。
我们唯一需要添加到我们的Widget中的是创建RenderObject并在我们的Widget变化时更新它。
class ChildSize extends SingleChildRenderObjectWidget {
// ...
@override
RenderObject createRenderObject(BuildContext context) {
return RenderChildSize().._widget = this;
}
@override
void updateRenderObject(BuildContext context, RenderChildSize renderObject) {
renderObject.._widget = this;
}
}
现在我们需要创建我们的RenderObject。
class RenderChildSize extends RenderBox
with RenderObjectWithChildMixin<RenderBox> {
var _widget = const ChildSize();
}
RenderObjectWithChildMixin是一个特殊的混合器,在使用SingleChildRenderObjectWidget时,我们必须添加它。这个混合器将处理所有无聊的东西(见下篇文章)。几乎每一个这样的帮助器都需要添加一些混合器。你可以在每个帮助器的文档中找到这些要求。
接下来要做的是布局我们的孩子。
class RenderChildSize ... {
// ...
var _lastSize = Size.zero;
@override
void performLayout() {
final child = this.child;
if (child != null) {
child.layout(constraints, parentUsesSize: true);
size = child.size;
} else {
size = constraints.smallest;
}
if (_lastSize != size) {
_lastSize = size;
_widget.onChildSizeChanged?.call(_lastSize);
}
}
}
在布局过程中,我们必须决定我们的RenderObject将有什么尺寸。为了做到这一点,我们需要布局我们的子对象。我们的RenderObject的尺寸将与子代的尺寸相同(我们没有任何的paddings、margins等等)。
这里有几个重要的说明。
- 我们必须将
parentUsesSize = true传递给child的布局函数,以便事后获得它的尺寸。否则,child.size会产生异常。由于这个标志,Flutter可以增加额外的优化。 - 有一种情况是,即使我们有一个子Widget,也没有子RenderObject。不是所有的Widget都有RenderObjects。在这种情况下,Flutter会尝试为我们提供最近的嵌套RenderObject,如果有的话。
在这个方法中,我们也会检查尺寸是否有变化,并在有变化时调用回调。
我们需要做的最后一件事是绘制我们的孩子,并对测试事件进行路由。
class RenderChildSize ... {
// ...
@override
void paint(PaintingContext context, Offset offset) {
final child = this.child;
if (child != null) {
context.paintChild(child, offset);
}
}
@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return child?.hitTest(result, position: position) == true;
}
}
而这里是结果。
你可以在我的GitHub上找到实现。 github.com/MatrixDev/F…
希望你喜欢它!
在下一篇文章中,我将展示如何在没有辅助工具的情况下手动实现同样的结果。
- 第1部分 - EllipsizedText
- Part 2.b - ChildSize (no helpers)
- Part 3.a- SimpleOverlay (with helpers)
- Part 3.b - SimpleOverlay (no helpers)