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

97 阅读3分钟

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

了解如何管理子RenderObjects以及如何在子Widget改变尺寸时接收回调......。

image.png

是时候发表我的第二篇文章了。这一次,它将是一个相当简单的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;
  }
}

而这里是结果。

1.gif

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

希望你喜欢它!

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


www.deepl.com 翻译