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();
}