将小部件离屏构建

6

我需要构建一个小部件(widget),只是为了获取它的位图(bitmap)。我不关心小部件是否在屏幕上。

所以我的问题是:我能否以某种方式在不使用屏幕视图层次结构的情况下“侧边”构建小部件(widget)?

我没有找到方法。因此,如果不可能,我能否在屏幕上构建小部件(widget),但实际上不显示它。 我尝试了Visibility但这会使RenderObject为空。使用Offstage时,在断言调用toImage()时会失败: Failed assertion: line 2752 pos 12: ‘!debugNeedsPaint’: is not true.

3个回答

3
EDIT: 看起来这在最近的Flutter版本中出了问题。不确定为什么,但我猜测Flutter现在会避免绘制,当它确定覆盖层完全不可见时。这种方法仍然可以使用,但需要与translate结合使用将其移动到屏幕外:https://gist.github.com/itsJoKr/ce5ec57bd6dedf74d1737c1f39481913 一些人推荐使用OverlayEntry,看起来是最好的解决方案。
您可以将OverlayEntry放在当前屏幕下方,这样它就不可见,并且使用maintainState:true它将被构建。 一个很大的优点是它更容易实现,因为它不会与当前的小部件树混合。
OverlayState overlayState = Overlay.of(context);
OverlayEntry entry = OverlayEntry(builder: (context) {
  return RepaintBoundary(key: key, child: yourWidget,); // Using RepaintBoundary to get RenderObject and convert to image
}, maintainState: true);
overlayState.insert(entry);
// doesn't work anymore
// overlayState.rearrange([entry], above: entry); // Didn't find how to insert it at the bottom of current overlays, so this should rearrange it so that our entry is at the bottom

当我这样做时,我会得到未处理的异常:'package:flutter/src/rendering/proxy_box.dart':失败的断言:行2904位置12:'!debugNeedsPaint':不为真。有什么线索可以解决这个问题吗? - Daniel Oliveira

1

这应该可以完成任务。我们创建了一个屏幕大小的区域,但是(在屏幕外),因此仍然可以作为树的一部分被捕获。

也可以在这里找到:https://gist.github.com/slightfoot/8eeadd8028c373df87f3a47bd4a35e36

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        accentColor: Colors.pinkAccent,
      ),
      home: ExampleScreen(),
    ),
  );
}

class ExampleScreen extends StatefulWidget {
  @override
  _ExampleScreenState createState() => new _ExampleScreenState();
}

class _ExampleScreenState extends State<ExampleScreen> {
  final _captureKey = GlobalKey<CaptureWidgetState>();
  Future<CaptureResult> _image;

  void _onCapturePressed() {
    setState(() {
      _image = _captureKey.currentState.captureImage();
    });
  }

  @override
  Widget build(BuildContext context) {
    return CaptureWidget(
      key: _captureKey,
      capture: Material(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text(
                'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.',
              ),
              SizedBox(height: 12.0),
              Container(
                width: 25.0,
                height: 25.0,
                color: Colors.red,
              ),
            ],
          ),
        ),
      ),
      child: Scaffold(
        appBar: AppBar(
          title: Text('Widget To Image Demo'),
        ),
        body: FutureBuilder<CaptureResult>(
          future: _image,
          builder: (BuildContext context, AsyncSnapshot<CaptureResult> snapshot) {
            return SingleChildScrollView(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Center(
                    child: RaisedButton(
                      child: Text('Capture Image'),
                      onPressed: _onCapturePressed,
                    ),
                  ),
                  if (snapshot.connectionState == ConnectionState.waiting)
                    Center(
                      child: CircularProgressIndicator(),
                    )
                  else if (snapshot.hasData) ...[
                    Text(
                      '${snapshot.data.width} x ${snapshot.data.height}',
                      textAlign: TextAlign.center,
                    ),
                    Container(
                      margin: const EdgeInsets.all(12.0),
                      decoration: BoxDecoration(
                        border: Border.all(color: Colors.grey.shade300, width: 2.0),
                      ),
                      child: Image.memory(
                        snapshot.data.data,
                        scale: MediaQuery.of(context).devicePixelRatio,
                      ),
                    ),
                  ],
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class CaptureWidget extends StatefulWidget {
  final Widget child;
  final Widget capture;

  const CaptureWidget({
    Key key,
    this.capture,
    this.child,
  }) : super(key: key);

  @override
  CaptureWidgetState createState() => CaptureWidgetState();
}

class CaptureWidgetState extends State<CaptureWidget> {
  final _boundaryKey = GlobalKey();

  Future<CaptureResult> captureImage() async {
    final pixelRatio = MediaQuery.of(context).devicePixelRatio;
    final boundary = _boundaryKey.currentContext.findRenderObject() as RenderRepaintBoundary;
    final image = await boundary.toImage(pixelRatio: pixelRatio);
    final data = await image.toByteData(format: ui.ImageByteFormat.png);
    return CaptureResult(data.buffer.asUint8List(), image.width, image.height);
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        final height = constraints.maxHeight * 2;
        return OverflowBox(
          alignment: Alignment.topLeft,
          minHeight: height,
          maxHeight: height,
          child: Column(
            children: <Widget>[
              Expanded(
                child: widget.child,
              ),
              Expanded(
                child: Center(
                  child: RepaintBoundary(
                    key: _boundaryKey,
                    child: widget.capture,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

class CaptureResult {
  final Uint8List data;
  final int width;
  final int height;

  const CaptureResult(this.data, this.width, this.height);
}

-2

在任何地方调用方法captureImage()之前,请执行以下操作

        if (boundary.debugNeedsPaint) {
            print("Waiting for boundary to be painted.");
            await Future.delayed(const Duration(milliseconds: 100));
            return captureImage();
          }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接