Created
October 6, 2024 15:46
-
-
Save jezell/eeb831f7c55a52a7e8954c777851a30c to your computer and use it in GitHub Desktop.
Screenshots
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 "package:flutter/material.dart"; | |
class FutureRegistry { | |
final List<Future> _futures = []; | |
void register(Future f) { | |
_futures.add(f); | |
f.whenComplete(() { | |
_futures.remove(f); | |
}); | |
} | |
Future<void> wait() async { | |
try { | |
await Future.wait(_futures); | |
} catch (_) { | |
} finally { | |
if (_futures.isNotEmpty) { | |
await wait(); | |
} | |
} | |
} | |
static FutureRegistry of(BuildContext context) { | |
return context.dependOnInheritedWidgetOfExactType<_FutureRegistryData>()!.registry; | |
} | |
static FutureRegistry? maybeOf(BuildContext context) { | |
return context.dependOnInheritedWidgetOfExactType<_FutureRegistryData>()?.registry; | |
} | |
static T track<T extends Future>(BuildContext context, T f) { | |
FutureRegistry.maybeOf(context)?.register(f); | |
return f; | |
} | |
Widget provide(Widget widget) { | |
return _FutureRegistryData(registry: this, child: widget); | |
} | |
} | |
class _FutureRegistryData extends InheritedWidget { | |
const _FutureRegistryData({ required super.child, required this.registry }); | |
final FutureRegistry registry; | |
@override | |
bool updateShouldNotify(_FutureRegistryData oldWidget) { | |
return oldWidget.registry != registry; | |
} | |
} |
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
// based on code here, but enhanced and fixed a bit | |
// https://medium.com/@mabud_alam/take-a-screenshot-of-any-widget-programmatically-in-flutter-4462ef2c4067 | |
import 'dart:async'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
// ignore: depend_on_referenced_packages | |
import 'package:flutter_localizations/flutter_localizations.dart'; | |
import 'dart:ui' as ui; | |
import 'future_registry.dart'; | |
/// We can use this class to convert any widget to an image. | |
/// It works even with widgets that are not visible on the screen. | |
class WidgetToImage { | |
/// Creates an image from the given widget. | |
/// | |
/// Parameters: | |
/// - widget: The widget to convert into an image. | |
/// - wait: Optional duration to wait before capturing the image. | |
/// - logicalSize: The logical size of the widget. Defaults to the window's physical size divided by the device pixel ratio. | |
/// - imageSize: The desired size of the output image. Defaults to the window's physical size. | |
/// | |
/// Returns: | |
/// A [Uint8List] representing the image data in PNG format, or null if the conversion failed. | |
static Future<Uint8List?> createImageFromWidget(Widget widget, | |
{Duration? wait, Size? logicalSize, Size? imageSize}) async { | |
final view = WidgetsBinding.instance.renderViews.first.flutterView; | |
final repaintBoundary = RenderRepaintBoundary(); | |
// Calculate logicalSize and imageSize if not provided | |
logicalSize ??= view.physicalSize / view.devicePixelRatio; | |
imageSize ??= view.physicalSize; | |
// Ensure logicalSize and imageSize have the same aspect ratio | |
assert(logicalSize.aspectRatio == imageSize.aspectRatio, | |
'logicalSize and imageSize must not be the same'); | |
// Create the render tree for capturing the widget as an image | |
final renderView = RenderView( | |
view: view, | |
child: RenderPositionedBox( | |
alignment: Alignment.center, child: repaintBoundary), | |
configuration: ViewConfiguration( | |
logicalConstraints: BoxConstraints( | |
minWidth: 0, | |
minHeight: 0, | |
maxWidth: logicalSize.width, | |
maxHeight: logicalSize.height), | |
devicePixelRatio: 1, | |
), | |
); | |
final pipelineOwner = PipelineOwner(); | |
BuildOwner? buildOwner; | |
RenderObjectToWidgetElement<RenderBox>? rootElement; | |
Completer? frame; | |
buildOwner = BuildOwner( | |
focusManager: FocusManager(), | |
onBuildScheduled: () { | |
frame = Completer(); | |
WidgetsBinding.instance.scheduleFrameCallback((_) { | |
buildOwner! | |
..buildScope(rootElement!) | |
..finalizeTree(); | |
// Flush layout, compositing, and painting operations | |
pipelineOwner | |
..flushLayout() | |
..flushCompositingBits() | |
..flushPaint(); | |
frame!.complete(); | |
}); | |
}); | |
pipelineOwner.rootNode = renderView; | |
renderView.prepareInitialFrame(); | |
final registry = FutureRegistry(); | |
final ro = RenderObjectToWidgetAdapter<RenderBox>( | |
container: repaintBoundary, | |
child: Directionality( | |
textDirection: TextDirection.ltr, | |
child: Localizations( | |
locale: PlatformDispatcher.instance.locale, | |
delegates: const [ | |
GlobalMaterialLocalizations.delegate, | |
GlobalWidgetsLocalizations.delegate, | |
GlobalCupertinoLocalizations.delegate, | |
], | |
child: Material( | |
child: MediaQuery.fromView( | |
view: view, | |
child: registry.provide(widget)))), | |
)); | |
// Attach the widget's render object to the render tree | |
rootElement = ro.attachToRenderTree(buildOwner); | |
buildOwner | |
..buildScope(rootElement) | |
..finalizeTree(); | |
// Flush layout, compositing, and painting operations | |
pipelineOwner | |
..flushLayout() | |
..flushCompositingBits() | |
..flushPaint(); | |
// wait for pending futures | |
await registry.wait(); | |
// Delay if specified | |
DateTime start = DateTime.now(); | |
do { | |
if (frame != null) { | |
await frame!.future; | |
} | |
buildOwner | |
..buildScope(rootElement) | |
..finalizeTree(); | |
// Flush layout, compositing, and painting operations | |
pipelineOwner | |
..flushLayout() | |
..flushCompositingBits() | |
..flushPaint(); | |
if (wait != null) { | |
// Build and finalize the render tree | |
await Future.delayed(const Duration(milliseconds: 200)); | |
} | |
if (frame == null) { | |
await Future.delayed(const Duration(milliseconds: 200)); | |
} | |
if (wait != null && DateTime.now().difference(start) > wait) { | |
break; | |
} | |
} while (frame == null || frame!.isCompleted); | |
buildOwner | |
..buildScope(rootElement) | |
..finalizeTree(); | |
// Flush layout, compositing, and painting operations | |
pipelineOwner | |
..flushLayout() | |
..flushCompositingBits() | |
..flushPaint(); | |
// Capture the image and convert it to byte data | |
final image = await repaintBoundary.toImage( | |
pixelRatio: imageSize.width / logicalSize.width); | |
final byteData = await image.toByteData(format: ui.ImageByteFormat.png); | |
// Return the image data as Uint8List | |
image.dispose(); | |
rootElement.visitChildren((Element element) { | |
// ignore: invalid_use_of_protected_member | |
rootElement!.deactivateChild(element); | |
}); | |
buildOwner.finalizeTree(); | |
renderView.dispose(); | |
pipelineOwner.rootNode = null; // renderView | |
pipelineOwner.dispose(); | |
rootElement.renderObject.dispose(); | |
rootElement.detachRenderObject(); | |
return byteData?.buffer.asUint8List(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment