Take screenshot hiding some widgets in Flutter

Viewed 581

I want to take a screenshot of a screen but I want to hide/blur/paint some Widgets in the resulting screenshot, but not in the app. Something like this:

What we see in the app What I want to get in the screenshot

The first picture is what I want to see in the app, no changes. The second picture is what I want to see in the screenshot, a specific widget painted.

My first thought was to calculate absolute coordinates of the specific widget, but in order to do this, I should add a GlobalKey to each of the widgets of the screen, and this is not feasible for my case.

How to do this without adding a GlobalKey to each of the widgets of the screen?

My current approach for taking a screenshot is:

final pixelRatio = MediaQuery.of(context).devicePixelRatio;
final boundary = boundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: pixelRatio);

This is what I'm using to get coordinates of a Widget that has a GlobalKey:

extension GlobalKeyExtension on GlobalKey {
  Rect? get globalPaintBounds {
    final renderObject = currentContext?.findRenderObject();
    var translation = renderObject?.getTransformTo(null).getTranslation();
    if (translation != null) {
      return renderObject!.paintBounds
          .shift(Offset(translation.x, translation.y));
    } else {
      return null;
    }
  }
}
2 Answers

screenshot widget is what you are looking for.

In your pubspec.yaml file add the following line under dependencies

dependencies:
  screenshot: ^0.3.0

Then you can use it like so. I just added some buttons and containers to show case it. You can customize it for your needs. Basically what this does is When you put a widget as a child of a Screenshot widget with a controller, you can capture it as an image.

import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart';

class ScreenCapture extends StatefulWidget {
  ScreenCapture({Key key}) : super(key: key);

  @override
  _ScreenCaptureState createState() => _ScreenCaptureState();
}

class _ScreenCaptureState extends State<ScreenCapture> {
  Uint8List _imageFile;
  Color captureColor = Colors.red, inAppColor = Colors.green, inAppImageBG;
  String capturingText = "Capturing State";
  String inAppDisplayText = "In App Display";
  String inAppText;
  ScreenshotController screenshotController = new ScreenshotController();

  @override
  void initState() {
    super.initState();
    inAppImageBG = inAppColor;
    inAppText = inAppDisplayText;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [capturedImageContainer(), inAppImage(), capture()],
      ),
    );
  }

  Widget capturedImageContainer() {
    return _imageFile != null
        ? Container(
            color: Colors.black,
            height: 100,
            width: 300,
            child: Image.memory(_imageFile),
          )
        : Container(color: Colors.black, height: 100, width: 300);
  }

  Widget inAppImage() {
    return Screenshot(
      controller: screenshotController,
      child: Container(
        color: inAppImageBG,
        height: 100,
        width: 300,
        child: Text(inAppText),
        alignment: Alignment.center,
      ),
    );
  }

  Widget capture() {
    return GestureDetector(
      onTap: () {
        doCapture();
      },
      child: Container(
        height: 100,
        width: 300,
        color: Colors.amber,
        child: Text("Capture"),
        alignment: Alignment.center,
      ),
    );
  }

  doCapture() async {
    Future.delayed(const Duration(milliseconds: 500), () {
      setState(() {
        inAppImageBG = captureColor;
        inAppText = capturingText + Random().nextInt(10).toString();
        Future.delayed(const Duration(milliseconds: 10), () {
          screenshotController
              .capture(delay: Duration(milliseconds: 1))
              .then((Uint8List image) async {
            setState(() {
              _imageFile = image;
              Future.delayed(const Duration(milliseconds: 1500), () {
                setState(() {
                  inAppImageBG = inAppColor;
                  inAppText = inAppDisplayText;
                });
              });
            });
          }).catchError((onError) {
            print(onError);
          });
        });
      });
    });
  }
}

I think you need to declare:

bool _hideWidget = false;

Next you this value with all widgets that you want to hide while taking screenshot:

floatingActionButton: _hideWidget ? const SizedBox.shrink() : FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),

In method that you use for taking screenshot, use next:

Future takeScreenShot() async {
    final RenderRepaintBoundary boundary = previewContainer.currentContext!.findRenderObject()! as RenderRepaintBoundary;

    if (boundary.debugNeedsPaint) {
      print("Waiting for boundary to be painted.");
      await Future.delayed(const Duration(milliseconds: 20));
      return takeScreenShot();
    }

    setState(() {
      _hideWidget = !_hideWidget;
    });
    await Future.delayed(const Duration(seconds: 1));
    final ui.Image image = await boundary.toImage(pixelRatio: 5);
    final directory = (await getApplicationDocumentsDirectory()).path;
    final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    final Uint8List pngBytes = byteData!.buffer.asUint8List();
    File imgFile = File('$directory/screenshot.png');
    print('$directory/screenshot.png');
    imgFile.writeAsBytes(pngBytes);
    await Future.delayed(const Duration(seconds: 1));
    setState(() {
      _hideWidget = !_hideWidget;
    });
  }

PS: in asynchronius methods you must use FutureDelayed for properly working of SetState

Related