Flutter提供了一個(gè)RepaintBoundaryWidget來實(shí)現(xiàn)截圖的功能,用RepaintBoundary包裹需要截取的部分,RenderRepaintBoundary可以將RepaintBoundary包裹的部分截取出來囤锉;然后通過boundary.toImage()方法轉(zhuǎn)化為ui.Image對(duì)象签舞,再使用image.toByteData()將image轉(zhuǎn)化為byteData次洼;最后通過File().writeAsBytes()存儲(chǔ)為文件對(duì)象:
RepaintBoundary(
? ? ? ? ? ? ? ? ? key: _repaintKey,
? ? ? ? ? ? ? ? ? child: Stack(
? ? ? ? ? ? ? ? ? ? alignment: Alignment.bottomRight,
? ? ? ? ? ? ? ? ? ? children: <Widget>[
? ? ? ? ? ? ? ? ? ? ? Image.asset(
? ? ? ? ? ? ? ? ? ? ? ? 'images/food01.jpeg',
? ? ? ? ? ? ? ? ? ? ? ? fit: BoxFit.cover,
? ? ? ? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? ? ? ? Icon(Icons.translate,),
? ? ? ? ? ? ? ? ? ? ],
? ? ? ? ? ? ? ? ? ),
? ? ? ? ? ? ? ? )
RenderRepaintBoundary boundary =
? ? ? ? ? ? ? ? ? ? _repaintKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
File(tempPath).writeAsBytes(pngBytes);
?需要注意的地方:
記住給RepaintBoundary添加一個(gè)key,因?yàn)槲覀冃枰ㄟ^這個(gè)key來找到當(dāng)前的RenderObject扯饶,生成一個(gè)RenderRepaintBoundary對(duì)象;
image.toByteData(format: format)可以自定義存儲(chǔ)格式,一般圖片為png格式尾序,但是要注意钓丰,如果你的RepaintBoundary包裹的部分沒有設(shè)置背景色,那么存儲(chǔ)出來的圖片可能會(huì)有背景色缺失的問題每币,boundary.toImage()并不會(huì)自動(dòng)添加一個(gè)你想要的白色背景携丁,這種情況在截取Text的時(shí)候會(huì)尤其明顯。
toImage({double pixelRatio = 1.0})中的pixelRatio參數(shù)是可以表面上提高圖片清晰度的脯爪。為什么說是表面上呢则北?因?yàn)檫@個(gè)參數(shù)是純px為單位的,和你的屏幕密度并沒有關(guān)系痕慢。以安卓為例尚揣,屏幕基礎(chǔ)寬高為360px*480px, 默認(rèn)輸出也是360px*480px的圖片文件掖举;如果你將pixelRatio設(shè)置為10快骗,那么你輸出的圖片文件大小將是3600px*4800px,但是展示這張圖的手機(jī)屏幕大小并不會(huì)改變塔次,圖變大了方篮,自然看起來“清晰”了。因此励负,這個(gè)值我建議取window.devicePixelRatio藕溅。
Flutter中獲取存儲(chǔ)路徑
我們可以通過官方插件path_provider來獲取APP的內(nèi)部和外部存儲(chǔ)路徑:
Directory tempDir = await getTemporaryDirectory(),等同于iOS中的NSCachesDirectoryAPI和Android中的getCacheDirAPI继榆;
Directory externalDir = await getExternalStorageDirectory()巾表,不支持iOS(會(huì)拋出UnsupportedError),等同于Android中的getExternalStorageDirectoryAPI略吨;
Directory applicationDir = await getApplicationDocumentsDirectory()集币,等同于iOS中的NSDocumentsDirectoryAPI和Android中的AppData目錄;
要注意的是getExternalStorageDirectory()方法翠忠,大多數(shù)情況下是訪問SDCard路徑鞠苟,因此即使在Android中調(diào)用,也要注意權(quán)限問題秽之,推薦使用permission_handler插件当娱。? ??還有就是存儲(chǔ)文件的時(shí)候要養(yǎng)成一個(gè)好習(xí)慣,先判斷下父目錄是否存在:
void _saveImage(Uint8List uint8List, Directory dir, String fileName)?
async {? bool isDirExist = await Directory(dir.path).exists();
? ? ? ? if(!isDirExist) Directory(dir.path).create();?
? ? ? ? ?······??
File(tempPath).writeAsBytes(uint8List);}
涂鴉
通過GestureDetector包裹需要繪制的區(qū)域政溃,收集用戶的手勢(shì)路徑信息趾访,通過canvas.drawLine()方法來繪制路徑:
List points = [];
GestureDetector( onPanStart: (details) { },?
? ? ? ? ? ? ? ? ?onPanUpdate: (details) {?
? ? ? ? ? ? ? ? ? ? ? ? ? RenderBox referenceBox = context.findRenderObject();?
? ? ? ? ? ? ? ? ? ? ? ? ?OffsetlocalPosition = referenceBox.globalToLocal(details.globalPosition);?
? ? ? ? ? ? ? ? ? ? ? ? ? state(() { points.add(localPosition); }); },?
? ? ? ? ? ? ? ? ? ?onPanEnd: (details) { }, )
for(int i = 0; i < points.length - 1; i++) {
? ? ? ? ? if(points[i] != null && points[i + 1] != null)? ? ?
? ? ? ? ? ? ? ?canvas.drawLine(points[i], points[i + 1], _linePaint);? ??
? ? ? ? ? ? ? }
為什么不直接用canvas.drawPoints()方法呢?
即使將PointMode設(shè)置為PointMode.lines董虱,你會(huì)發(fā)現(xiàn)扼鞋,繪制出來的點(diǎn)集合并不是無縫連接在一起的申鱼,看起來就像是虛線一樣:
更新:評(píng)論有人說可以使用PointMode.polygon,polygon確實(shí)可以繪制連續(xù)的曲線云头,但是有兩個(gè)缺點(diǎn):一是繪制不如drawLine流暢捐友,延遲會(huì)大一點(diǎn),因?yàn)闀簳r(shí)的實(shí)現(xiàn)方式是用戶手指每動(dòng)一次都會(huì)重繪一次溃槐,消耗比較大匣砖;二是繪制出來的線條不如直接繪制line來得圓潤。我之前沒有深度考慮drawPoints的一個(gè)原因就是延遲太大昏滴。以后如果能找到更好的刷新機(jī)制猴鲫,我會(huì)嘗試使用drawPoints來繪制的。
因此我們還是使用drawLine()強(qiáng)行將所有點(diǎn)連接起來谣殊。
用戶繪制完成后拂共,依然是使用RepaintBoundary來保存圖片就可以啦~~~