在屏幕外构建小部件

时间:2019-08-08 08:29:20

标签: flutter

我需要构建一个小部件以获取其位图。我不在乎屏幕上有小部件。

所以我的问题是:我可以以某种方式在不使用屏幕视图层次结构的情况下“在侧面”构建小部件吗?

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

3 个答案:

答案 0 :(得分:3)

编辑:看起来这在Flutter的最新版本中中断了。不知道为什么,但是我想Flutter现在会在确定完全不可见叠加层时避免绘制。该方法仍然可以使用,但需要与翻译结合使用才能将其移出屏幕: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

答案 1 :(得分:0)

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

也可以在这里找到: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 :(得分:-1)

在任何地方调用方法captureImage()之前

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