-
-
Save mockturtl/2e2c1a67632470eb8f83981f7369e56f to your computer and use it in GitHub Desktop.
Capture Flutter Widgets as Image but Off Screen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), home: ExampleScreen())); | |
} | |
class CaptureResult { | |
final Uint8List data; | |
final int width; | |
final int height; | |
const CaptureResult(this.data, this.width, this.height); | |
} | |
class CaptureWidget extends StatefulWidget { | |
final Widget child; | |
final Widget capture; | |
const CaptureWidget({ | |
super.key, | |
required this.capture, | |
required this.child, | |
}); | |
@override | |
State<CaptureWidget> createState() => CaptureWidgetState(); | |
} | |
class CaptureWidgetState extends State<CaptureWidget> { | |
final _boundaryKey = GlobalKey(); | |
@override | |
Widget build(BuildContext context) => | |
LayoutBuilder(builder: (context, constraints) { | |
final height = constraints.maxHeight * 2; | |
return Stack(fit: StackFit.passthrough, children: <Widget>[ | |
widget.child, | |
Positioned( | |
left: 0, | |
right: 0, | |
top: height, | |
height: height, | |
child: Center( | |
child: RepaintBoundary( | |
key: _boundaryKey, child: widget.capture))), | |
]); | |
}); | |
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 ?? 0, image?.height ?? 0); | |
} | |
} | |
class ExampleScreen extends StatefulWidget { | |
@override | |
State<ExampleScreen> createState() => _ExampleScreenState(); | |
} | |
class _ExampleScreenState extends State<ExampleScreen> { | |
final _captureKey = GlobalKey<CaptureWidgetState>(); | |
Future<CaptureResult>? _image; | |
@override | |
Widget build(BuildContext context) => CaptureWidget( | |
key: _captureKey, | |
capture: Material( | |
child: Padding( | |
padding: const EdgeInsets.all(8), | |
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[ | |
const Text( | |
'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.', | |
), | |
const SizedBox(height: 12), | |
Container(width: 25, height: 25, color: Colors.red), | |
]))), | |
child: Scaffold( | |
appBar: AppBar(title: const Text('Widget To Image Demo')), | |
body: FutureBuilder<CaptureResult>( | |
future: _image, | |
builder: (context, snapshot) => SingleChildScrollView( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Center( | |
child: ElevatedButton( | |
onPressed: _onCapturePressed, | |
child: const Text('Capture Image'))), | |
if (snapshot.connectionState == ConnectionState.waiting) | |
const Center(child: CircularProgressIndicator()) | |
else if (snapshot.hasData) ...[ | |
Text( | |
'${snapshot.data!.width} x ${snapshot.data!.height}', | |
textAlign: TextAlign.center), | |
for (var i = 0; i < 50; i++) | |
Container( | |
margin: const EdgeInsets.all(12), | |
decoration: BoxDecoration( | |
border: Border.all( | |
color: Colors.grey.shade300, width: 2)), | |
child: Image.memory(snapshot.data!.data, | |
scale: MediaQuery.of(context) | |
.devicePixelRatio)), | |
], | |
]))))); | |
void _onCapturePressed() { | |
_image = _captureKey.currentState?.captureImage(); | |
setState(() {}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment