The introduction

The sharing function of an App usually has screen shots to share. Generally, screenshots of Flutter are realized by RepaintBoundary. The specific implementation is very simple and will not be described here. This article mainly talks about two points: long screenshots and picture stitching.

1. Long shot

In actual situations, the area to be captured may exceed one screen, that is, a long screenshot is required. This area is typically nested in scrollable containers such as SingleChildScroolView. If we directly cover the rolling container with a RepaintBoundary, for example:

RepaintBoundary( key: key , child: SingleChildScrollView( child: Column( children:[ widget1, widget2, widget3, ... ] ),),);Copy the code

When you get a screenshot, you’ll notice that the screenshot only has the area currently displayed on the screen! The reason is simple: the RepaintBoundary measures the width and height of the immediate child widget, and the maximum width and height of its immediate child widget (SingleChildScrollView in the example) is the screen width. The solution is simple: place the RepaintBoundary over the actual Column (the width of the widget is exactly what we want it to be). The above example should be changed to:

SingleChildScrollView( child: RepaintBoundary( key: key , child: Column( children:[ widget1, widget2, widget3, ... ] ),),);Copy the code

Another point to note is that if the page background color is set on the outer layer of the RepaintBoundary, then the screenshot will have a transparent background and you need to set the background color on the inner layer.

2. Picture stitching

Ideally, the long screenshot above would be SingleChildScrollView, which allows usto RepaintBoundary on child, but in reality it could also be CustomScrollView, which is strictly restricted to children, We may need to use a container like SliverToBoxAdapter and have a RepaintBoundary over each child, for example:

CustomScrollView(
          slivers: <Widget>[
            ///appbarSliverAppBar( flexibleSpace: RepaintBoundary( key: key, child: FlexibleSpaceBar(... ) ,),),/ / / contentSliverToBoxAdapter( child: RepaintBoundary( key: key2, child:Column(...) )),,)Copy the code

But then it turns into a small area a screenshot. Or, many businesses in the real world will require that promotional content, such as QR codes, be stitched together at the bottom of the screenshot. These situations require us to carry on the picture Mosaic.

For image stitching, I use paint to control the positions of different images, draw them on the Canvas, and export the images.

The main points are:

  1. Calculate the maximum width/height according to the width and height of each image to be spliced
  2. You also need to determine orientation if you want to support horizontal and vertical alignment
  3. Images may vary in width and height, and need to be scaled to keep them consistent (vertical width, horizontal height).

The concrete implementation is as follows:

(1) Determine the parameters that are open to external input. ImageList List of images (mandatory), direction, fit whether to zoom to keep the list consistent.

(2) Calculate the maximum picture width/height. Traverse the imageList and judge the direction. If it is vertical, take the maximum image width as maxWidth. If it is horizontal, take the maximum image height as maxHeight.

 /// Calculate the maximum image width/height
 int maxWidth = 0;
 int maxHeight = 0;
 imageList.forEach((image) {
      if (direction == Axis.vertical) {
        if (maxWidth < image.width) maxWidth = image.width;
      } else {
        if(maxHeight < image.height) maxHeight = image.height; }});// create variable record total height and total width
int totalHeight = maxHeight;
int totalWidth = maxWidth;
Copy the code

(3) Initialize the drawing control.

    ui.PictureRecorder recorder = ui.PictureRecorder();
    final paint = Paint();
    Canvas canvas = Canvas(recorder);
Copy the code

(4) Specific drawing according to fit. If fit, you need to scale the canvas to match the width/height and record the coordinate points

  double dx = 0;
  double dy = 0;
    //draw images into canvas
    imageList.forEach((image) {
      double scaleDx = dx;
      double scaleDy = dy;
      double imageHeight = image.height.toDouble();
      double imageWidth = image.width.toDouble();
      if (fit) {
        //scale the image to same width/height
        canvas.save();
        if(direction == Axis.vertical && image.width ! = maxWidth) { canvas.scale(maxWidth / image.width); scaleDy *= imageWidth/maxWidth; imageHeight *= maxWidth/imageWidth; }else if(direction == Axis.horizontal && image.height ! = maxHeight) { canvas.scale(maxHeight / image.height); scaleDx *= imageHeight/maxHeight; imageWidth *= maxHeight/imageHeight; } canvas.drawImage(image, Offset(scaleDx, scaleDy), paint); canvas.restore(); }else {
        //draw directly
        canvas.drawImage(image, Offset(dx, dy), paint);
      }
      //accumulate dx/dy
      if (direction == Axis.vertical) {
        dy += imageHeight;
        totalHeight += imageHeight.floor();
      } else{ dx += imageWidth; totalWidth += imageWidth.floor(); }});Copy the code

(5) Output image. The default format is UI. Image. If you need Uint8List or File, you need to transform it by yourself.

return recorder.endRecording().toImage(totalWidth, totalHeight);
Copy the code

Effect preview:

The merge_images control has been uploaded to the PUB market. Merge_images comes with the ability to convert image formats and display images directly from widgets.