Flutter 在屏幕外构建小部件

Flutter 在屏幕外构建小部件,flutter,Flutter,我需要建立一个小部件只是为了得到它的位图。我不在乎小部件出现在屏幕上 所以我的问题是:我是否可以在不使用屏幕视图层次结构的情况下“在侧面”构建小部件 我没有找到一个办法。因此,如果不可能,我可以在屏幕上构建小部件,但不实际显示它。 我尝试了可见性,但这将使RenderObject为空。使用Offstage时,在断言时调用toImage()将失败:失败的断言:第2752行位置12:'!debugNeedsPaint':不正确。这应该可以完成任务。我们创建一个屏幕大小的区域。。但是(在屏幕外),所以

我需要建立一个小部件只是为了得到它的位图。我不在乎小部件出现在屏幕上

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

我没有找到一个办法。因此,如果不可能,我可以在屏幕上构建小部件,但不实际显示它。
我尝试了
可见性
,但这将使
RenderObject
为空。使用
Offstage
时,在断言时调用
toImage()
将失败:
失败的断言:第2752行位置12:'!debugNeedsPaint':不正确。

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

也可在此处找到:

导入'dart:async';
导入“dart:键入的_数据”;
将“dart:ui”导入为ui;
进口“包装:颤振/材料.省道”;
导入“package:flatter/rendering.dart”;
void main(){
runApp(
材料聚丙烯(
主题:主题数据(
原色样本:颜色。靛蓝,
accentColor:Colors.pinkAccent,
),
主页:ExampleScreen(),
),
);
}
类ExampleScreen扩展StatefulWidget{
@凌驾
_ExampleScreateState()=>新建;
}
类\u示例ScreenState扩展状态{
最终_captureKey=GlobalKey();
未来形象;
void _onCapturePressed(){
设置状态(){
_image=_captureKey.currentState.captureImage();
});
}
@凌驾
小部件构建(构建上下文){
返回捕获窗口小部件(
密钥:_captureKey,
捕获:材料(
孩子:填充(
填充:常数边集全部(8.0),
子:列(
mainAxisSize:mainAxisSize.min,
儿童:[
正文(
“这些小部件在屏幕上不可见,但仍可通过重新绘制边界捕获。”,
),
尺寸箱(高度:12.0),
容器(
宽度:25.0,
身高:25.0,
颜色:颜色,红色,
),
],
),
),
),
孩子:脚手架(
appBar:appBar(
标题:文本(“小部件到图像演示”),
),
正文:未来建设者(
未来:_形象,
生成器:(BuildContext上下文,异步快照){
返回SingleChildScrollView(
子:列(
mainAxisAlignment:mainAxisAlignment.center,
儿童:[
居中(
孩子:升起按钮(
子项:文本(“捕获图像”),
onPressed:_onCapturePressed,
),
),
if(snapshot.connectionState==connectionState.waiting)
居中(
子对象:CircularProgressIndicator(),
)
else if(snapshot.hasData)[
正文(
“${snapshot.data.width}x${snapshot.data.height}”,
textAlign:textAlign.center,
),
容器(
边距:所有常数边集(12.0),
装饰:盒子装饰(
边框:边框。全部(颜色:Colors.grey.shade300,宽度:2.0),
),
孩子:图像。记忆(
snapshot.data.data,
scale:MediaQuery.of(context).devicePixelRatio,
),
),
],
],
),
);
},
),
),
);
}
}
类CaptureWidget扩展StatefulWidget{
最后一个孩子;
最终小部件捕获;
常量捕获控件({
关键点,
这个,捕获,,
这个孩子,
}):super(key:key);
@凌驾
CaptureWidgetState createState()=>CaptureWidgetState();
}
类CaptureWidgetState扩展了状态{
final _boundaryKey=GlobalKey();
Future captureImage()异步{
最终pixelRatio=MediaQuery.of(context).devicePixelRatio;
final boundary=\u boundaryKey.currentContext.FindEnderObject()作为RenderPaintBoundary;
最终图像=等待边界.toImage(像素比率:像素比率);
最终数据=wait image.toByteData(格式:ui.ImageByteFormat.png);
返回CaptureResult(data.buffer.asUint8List()、image.width、image.height);
}
@凌驾
小部件构建(构建上下文){
返回布局生成器(
生成器:(BuildContext上下文,BoxConstraints){
最终高度=约束。最大高度*2;
回流溢流箱(
对齐:alignment.topLeft,
最小高度:高度,
maxHeight:高度,
子:列(
儿童:[
扩大(
child:widget.child,
),
扩大(
儿童:中心(
子:重新绘制边界(
key:_boundaryKey,
child:widget.capture,
),
),
),
],
),
);
},
);
}
}
类捕获结果{
最终UINT8列表数据;
最终整型宽度;
最终整数高度;
常量捕获结果(this.data、this.width、this.height);
}

编辑:这看起来像是在最近版本的颤振中出现的。不知道为什么,但我猜当颤振确定覆盖层根本不可见时,它现在将避免绘制。此方法仍然可以使用,但需要与translate结合才能将其移出屏幕:

一些人建议使用
OverlayEntry
,它看起来是最好的解决方案

您可以将
OverlayEntry
放在当前屏幕下方,使其不可见,并使用
maintaintState:true
将构建该屏幕。 一个很大的优点是它更容易实现,因为它不与th混合
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);
}
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
        if (boundary.debugNeedsPaint) {
            print("Waiting for boundary to be painted.");
            await Future.delayed(const Duration(milliseconds: 100));
            return captureImage();
          }