引言
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) 確定開放給外界傳入的引數。 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
,若需要Uint8List
或File
等,需要自己轉換。
return recorder.endRecording().toImage(dx.floor(), dy.floor());
複製程式碼
效果預覽:
圖片拼接的控制元件已經傳到pub市場:merge_images, 裡面附帶了一些圖片格式的轉換及直接使用widget顯示的功能,有需要的可以使用。