Skip to content

Instantly share code, notes, and snippets.

@mockturtl
Forked from slightfoot/capture_widget.dart
Created May 29, 2025 14:12
Show Gist options
  • Save mockturtl/2e2c1a67632470eb8f83981f7369e56f to your computer and use it in GitHub Desktop.
Save mockturtl/2e2c1a67632470eb8f83981f7369e56f to your computer and use it in GitHub Desktop.
Capture Flutter Widgets as Image but Off Screen
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