Flutter截圖分享要點

man_in_cloak發表於2020-04-06

引言

App的分享功能一般都會有螢幕截圖分享。一般Flutter的截圖功能都是通過RepaintBoundary來實現的,具體實現很簡單,這裡不做闡述。本文章主要講兩點:長截圖及圖片拼接。

1.長截圖

實際需求中,需要截圖的區域可能會超過一屏,即需要長截圖。這個區域一般巢狀在可滾動容器中,例如SingleChildScroolView等。 如果我們直接在滾動容器外層套上RepaintBoundary,例如:

RepaintBoundary(
  key: key ,
  child: SingleChildScrollView(
    child: Column(
      children:[
            widget1,
            widget2,
            widget3,
            ...
            ]
            ),
          ),
        );
複製程式碼

獲取到截圖就會發現:截圖僅有當前螢幕上正在顯示的區域! 原因其實很簡單:RepaintBoundary會測量直接子widget的寬高,而此時它的直接子widget(例子中的SingleChildScrollView)的最大寬高就是螢幕寬高。 解決方法也很簡單:把RepaintBoundary套在真正要截的外層,一般就是Column(這個widget的寬高就是我們實際想截的寬高)。上面的例子就要改成:

SingleChildScrollView(
  child: RepaintBoundary(
    key: key ,
    child: Column(
      children:[
            widget1,
            widget2,
            widget3,
            ...
            ]
            ),
          ),
        );
複製程式碼

還需要注意的一點是,如果頁面背景顏色設定在RepaintBoundary外層,那麼截圖到的圖片是透明背景的,此時就需要在內層設定一下背景顏色了。

2.圖片拼接

上面長截圖的理想情況是SingleChildScrollView這種允許我們在child套一層RepaintBoundary,但實際中也可能是CustomScrollView這種嚴格限制children型別的,此時我們可能就需要藉助類似SliverToBoxAdapter這種容器,並且在每個child上都套一個RepaintBoundary,例如:

CustomScrollView(
          slivers: <Widget>[
            ///appbar
            SliverAppBar(
              flexibleSpace: RepaintBoundary(
                key: key,
                child: FlexibleSpaceBar(... ),
              ),
            ),
            ///內容
            SliverToBoxAdapter(
              child: RepaintBoundary(
                key: key2,
                child:Column(...)
                )
            ),
          ],
        )
複製程式碼

但是這樣就變成一小塊區域一張截圖了。或者,實際業務中很多都會要求在截圖的底部拼接推廣內容,比如二維碼等。這些情況就要求我們進行圖片拼接了。

對於圖片拼接,我的實現思路是:使用paint,控制不同圖片的位置,然後在canvas上繪製出來,最後再輸出圖片。

其中的要點有:

  1. 根據各個需要拼接的圖片的寬高計算最大寬/高
  2. 如果要支援橫向和縱向排列,還需要確定方向
  3. 圖片之間可能寬高不同,需要縮放以保持一致(豎向排列寬度要一致,橫向排列高度要一致)

具體實現如下:

(1) 確定開放給外界傳入的引數。 imageList圖片list(必傳)、direction方向、fit是否縮放以保持一致。

(2) 計算最大圖片寬/高。 遍歷imageList,判斷direction,若縱向則取最大圖片寬作為maxWidth;若橫向則取最大圖片高作為maxHeight。

 ///計算最大圖片寬/高
 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;
      }
    });
複製程式碼

(3)初始化繪製控制元件。

    ui.PictureRecorder recorder = ui.PictureRecorder();
    final paint = Paint();
    Canvas canvas = Canvas(recorder);
複製程式碼

(4)根據是否fit進行具體繪製。 若fit,需要縮放canvas以匹配寬/高,並記錄座標點

 double dx = 0;//x座標
 double dy = 0;//y座標
 imageList.forEach((image) {
      double scaleDx = dx;//縮放後的x座標
      double scaleDy = dy;//縮放後的y座標
      if (fit) {
        //需要縮放
        canvas.save();
        if (direction == Axis.vertical && image.width != maxWidth) {
          canvas.scale(maxWidth / image.width);
          scaleDy *= image.width / maxWidth;
        } else if (direction == Axis.horizontal &&
            image.height != maxHeight) {
          canvas.scale(maxHeight / image.height);
          scaleDx *= image.height / maxHeight;
        }
        canvas.drawImage(image, Offset(scaleDx, scaleDy), paint);
        canvas.restore();
      } else {
        //不縮放,直接繪製
        canvas.drawImage(image, Offset(dx, dy), paint);
      }
      //累加記錄dx/dy
      if (direction == Axis.vertical) {
        dy += scaleDy;
      } else {
        dx += scaleDx;
      }
    });
複製程式碼

(5)輸出image。 預設格式為ui.Image,若需要Uint8ListFile等,需要自己轉換。

return recorder.endRecording().toImage(dx.floor(), dy.floor());
複製程式碼

效果預覽:

Flutter截圖分享要點

圖片拼接的控制元件已經傳到pub市場:merge_images, 裡面附帶了一些圖片格式的轉換及直接使用widget顯示的功能,有需要的可以使用。

相關文章