-
-
Save Gorniv/bd740fb0bed93087c9ee20c19b9c6cea to your computer and use it in GitHub Desktop.
Flutter get screenshot from canvas layer
This file contains 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
// Just example how to save screenshot | |
import 'dart:io'; | |
import 'package:path/path.dart'; | |
import 'package:path_provider/path_provider.dart'; | |
import 'package:gallery_saver/gallery_saver.dart'; | |
@override | |
Future<void> saveScreenshot() async { | |
final screenshot = await screenshotController.takeScreenshot(); | |
final dir = await getTemporaryDirectory(); | |
final file = File(join(dir.path, 'screenshot_${DateTime.now().toIso8601String()}.png')); | |
await file.writeAsBytes( | |
screenshot.buffer.asUint8List(screenshot.offsetInBytes, screenshot.lengthInBytes), | |
); | |
await GallerySaver.saveImage(file.path).then<void>( | |
(success) { | |
if (success != true) { | |
throw UnsupportedError('Sad story'); | |
} | |
}, | |
); | |
print('Well done'); | |
} |
This file contains 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:typed_data' as td; | |
import 'dart:ui' as ui; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'package:meta/meta.dart'; | |
/// Scope to create a screenshot of the underlying layer of the canvas "tree" of widgets | |
/// You can interact through the [ScreenshotController] - for example, from the business logic layer | |
@immutable | |
class ScreenshotScope extends StatelessWidget { | |
final Widget child; | |
final ScreenshotController? controller; | |
const ScreenshotScope({ | |
required this.child, | |
this.controller, | |
Key? key, | |
}) : super(key: key); | |
/// Take a screenshot from [ScreenshotScope] | |
static Future<td.ByteData> takeScreenshot(BuildContext context) => _of(context).takeScreenshot(); | |
/// Get [_ScreenshotScopeState] state from context | |
static ScreenshotScopeState _of(BuildContext context) => context.findAncestorStateOfType<ScreenshotScopeState>() ?? _notInScope(); | |
@alwaysThrows | |
static Never _notInScope() => throw UnsupportedError('Not in ScreenshotScope scope'); | |
@override | |
Widget build(BuildContext context) => RepaintBoundary( | |
child: _ScreenshotScope( | |
controller: controller, | |
child: child, | |
), | |
); | |
} | |
@immutable | |
class _ScreenshotScope extends StatefulWidget { | |
final Widget child; | |
final ScreenshotController? controller; | |
const _ScreenshotScope({ | |
required this.child, | |
this.controller, | |
Key? key, | |
}) : super(key: key); | |
@override | |
State<_ScreenshotScope> createState() => ScreenshotScopeState(); | |
} | |
class ScreenshotScopeState extends State<_ScreenshotScope> { | |
@override | |
void initState() { | |
super.initState(); | |
widget.controller?.addCallback(takeScreenshot); | |
} | |
@override | |
void didUpdateWidget(_ScreenshotScope oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
if (!identical(widget.controller, oldWidget.controller)) { | |
widget.controller?.addCallback(takeScreenshot); | |
} | |
} | |
Future<td.ByteData> takeScreenshot() async { | |
final boundary = context.findAncestorRenderObjectOfType<RenderRepaintBoundary>(); | |
if (boundary is! RenderRepaintBoundary) { | |
throw UnsupportedError('Nearest RenderObject not RenderRepaintBoundary'); | |
} | |
final image = await boundary.toImage(pixelRatio: 1); | |
final bytes = await image.toByteData(format: ui.ImageByteFormat.png); | |
if (bytes is! td.ByteData) { | |
throw UnsupportedError('Error while converting screenshot image to byte array'); | |
} | |
return bytes; | |
} | |
@override | |
Widget build(BuildContext context) => widget.child; | |
} | |
/// Controller for creating a screenshot of the underlying tree | |
/// One instance can be installed only to one scope at a time | |
@sealed | |
@immutable | |
class ScreenshotController { | |
/// Factory for creating a controller | |
factory ScreenshotController({String? debugId}) = ScreenshotController._; | |
/// Factory for creating / getting a canonic literal | |
@literal | |
const factory ScreenshotController.literal({required String debugId}) = ScreenshotController._literal; | |
// ignore: prefer_const_constructors_in_immutables | |
ScreenshotController._({this.debugId}); | |
@literal | |
const ScreenshotController._literal({required this.debugId}); | |
/// The main controller, a canonical instance literal, can be used as a singleton | |
static const ScreenshotController main = ScreenshotController.literal(debugId: 'main'); | |
@protected | |
static final Expando<Future<td.ByteData> Function()> _callbacks = Expando<Future<td.ByteData> Function()>(); | |
/// Controller ID for debugging purposes | |
/// is also useful for creating multiple canonic literals | |
@internal | |
@protected | |
@nonVirtual | |
@visibleForTesting | |
final String? debugId; | |
@mustCallSuper | |
Future<td.ByteData> takeScreenshot() => _callbacks[this]?.call() ?? _missingWidget(); | |
@alwaysThrows | |
Never _missingWidget() => throw UnsupportedError('The controller was not installed to the corresponding ScreenshotScope'); | |
@internal | |
@protected | |
@nonVirtual | |
@visibleForTesting | |
void addCallback(Future<td.ByteData> Function() takeScreenshot) => _callbacks[this] = takeScreenshot; | |
@override | |
bool operator ==(Object other) => identical(other, this); | |
@override | |
int get hashCode => debugId?.hashCode ?? super.hashCode; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment