[Flutter翻译]在Flutter中编写自定义Widget(第3.b部分)--SimpleOverlay(无辅助工具)

109 阅读4分钟

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

学习如何从头开始编写带有多个孩子的自定义Widgets,不需要任何帮助。

image.png

按照承诺,现在我将展示如何编写没有任何助手的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方法--我们不需要调用adoptChilddropChild,因为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应该优先考虑。

下面是结果。

1.gif

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

希望你喜欢它!


www.deepl.com 翻译