【问题标题】:How to prevent "Duplicate GlobalKey detected in widget tree" when building Widget offscreen在屏幕外构建小部件时如何防止“在小部件树中检测到重复的全局键”
【发布时间】:2022-01-04 15:04:26
【问题描述】:

我正在尝试将小部件导出为图像,它比实际视口更宽。

我找到了这个方法:

Future<Uint8List?> createImageFromWidget(Widget widget,
    {Size? logicalSize, Size? imageSize}) async {
  final repaintBoundary = RenderRepaintBoundary();

  logicalSize ??= ui.window.physicalSize / ui.window.devicePixelRatio;
  imageSize ??= ui.window.physicalSize;

  assert(logicalSize.aspectRatio == imageSize.aspectRatio);

  final renderView = RenderView(
    window: ui.window,
    child: RenderPositionedBox(child: repaintBoundary),
    configuration: ViewConfiguration(
      size: logicalSize,
    ),
  );

  final pipelineOwner = PipelineOwner();
  final buildOwner = BuildOwner(focusManager: FocusManager());

  pipelineOwner.rootNode = renderView;
  renderView.prepareInitialFrame();

  final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: repaintBoundary,
    child: widget,
  ).attachToRenderTree(buildOwner);

  buildOwner.buildScope(rootElement);
  buildOwner.finalizeTree();

  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();

  final image = await repaintBoundary.toImage(
      pixelRatio: imageSize.width / logicalSize.width);
  final byteData = await image.toByteData(format: ui.ImageByteFormat.png);

  if (byteData == null) return null;

  return byteData.buffer.asUint8List();
}

据我了解,此方法在新的 Offscreen-Widgettree 中构建引用的 Widget,并将视口大小设置为需要导出的 Widget 的大小。

这种方法本身有效,但这是我的问题:

我正在引用需要由GlobalKey 导出的Widget

所以它看起来像这样:

Repaintboundary(
 key: myGlobalKey,
 child : WidgetIWantToExport()
);

void exportWidget(){
 final currentWidgetSize = myGlobalKey.currentContext!.size!;
 final keyWidget = myGlobalKey.currentWidget!;
 final pngBytes = await createImageFromWidget(
   keyWidget,
   logicalSize: Size(currentWidgetSize.width, currentWidgetSize.height),
   imageSize: Size(currentWidgetSize.width, currentWidgetSize.height),
 );
 //exporting bytes
}

所以当我运行这个方法时,.attachToRenderTree() 会抛出一个错误:'_elements.contains(element)': is not true. ... Duplicate GlobalKey detected in widget tree.

所以我假设问题是,它试图在新的 RenderTree 中构建一个具有相同 GobalKey 的小部件,这会引发此错误。

我该如何解决这个问题?

【问题讨论】:

    标签: flutter dart flutter-layout


    【解决方案1】:

    我通过访问我通过 Key 引用的 Repaintboundary 的子级解决了这个问题。

    代码如下所示:

    
    /// Creates an image from the given widget by first spinning up a element and render tree,
    /// and then creating an image via a [RepaintBoundary].
    ///
    /// The final image will be of size [exportViewportSize].
    /// If no [exportViewportSize] is supplied, the image will be of size [logicalSize], which has a fallback value.
    Future<Uint8List?> createImageFromWidget(Widget widget,
        {Size? logicalSize, Size? imageSize, Size? exportViewportSize}) async {
      final renderRepaintBoundary = RenderRepaintBoundary();
    
      final repaintBoundary = widget as RepaintBoundary; //casting the widget as RepaintBoundary
    
      final renderWidget = repaintBoundary.child!; //Accessing its child widget
    
      logicalSize ??= ui.window.physicalSize / ui.window.devicePixelRatio;
      imageSize ??= ui.window.physicalSize;
    
      assert(logicalSize.aspectRatio == imageSize.aspectRatio);
      final renderView = RenderView(
        window: ui.window,
        child: RenderPositionedBox(child: renderRepaintBoundary),
        configuration: ViewConfiguration(
          size: exportViewportSize ?? logicalSize,
        ),
      );
    
      final pipelineOwner = PipelineOwner();
      final buildOwner = BuildOwner(focusManager: FocusManager());
    
      pipelineOwner.rootNode = renderView;
      renderView.prepareInitialFrame();
      final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderRepaintBoundary,
        child: MaterialApp(
          home: renderWidget,
        ),
      ).attachToRenderTree(buildOwner);
    
      buildOwner.buildScope(rootElement);
      buildOwner.finalizeTree();
    
      pipelineOwner.flushLayout();
      pipelineOwner.flushCompositingBits();
      pipelineOwner.flushPaint();
    
      final image = await renderRepaintBoundary.toImage(
          pixelRatio: imageSize.width / logicalSize.width);
      final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    
      if (byteData == null) return null;
    
      return byteData.buffer.asUint8List();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-12
      • 2018-08-28
      • 2020-04-14
      • 2021-05-17
      • 2019-04-28
      • 2021-06-25
      • 2022-09-27
      • 1970-01-01
      相关资源
      最近更新 更多