小菜因特别需求想嘗試一下 Flutter 頁面截屏并将圖檔儲存在本地的功能,記錄一下嘗試過程。
RepaintBoundary
Flutter 提供了支援截屏的 RepaintBoundary,在需要截取部分的外層嵌套,也可以截取某一子 Widget 内容;RepaintBoundary 的結構很簡單,通過 key 來判斷截取的 RenderObject,最終生成一個 RenderRepaintBoundary 對象;
const RepaintBoundary({ Key key, Widget child }) : super(key: key, child: child);
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: globalKey,
child: Scaffold(
body: Stack(children: <Widget>[
Image.asset('img/bg.jpg', width: _w, fit: BoxFit.fill),
Container(child: CustomPaint(painter: StarCanvas(_w, _h, p, s, st))),
itemWid(1, 2),
itemWid(1, 1),
itemWid(0, 1),
itemWid(0, 2),
])));
}
ui.Image
通過 RenderRepaintBoundary 擷取的對象 .toImage() 後轉為 ui.Image 類型位元組流,最終存儲為 png 格式,在轉為常用的 Uint8List 存儲在記憶體中,借助 image.memory() 方式展示在具體位置;而目前隻是擷取到圖檔的流資訊,僅可用于操作,還未存儲在本地;
toByteData() 生成的資料格式一般分三種:
- rawRgba:未解碼的 byte;
- rawUnmodified:未解碼且未修改的 byte,如灰階圖;
- png 為我們常用的 PNG 圖檔;
Future<Uint8List> _capturePng(globalKey) async {
RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List picBytes = byteData.buffer.asUint8List();
return picBytes;
}
Directory
若需要存儲本地,跟 Android/iOS 類似,首先擷取存儲路徑,再進行存儲操作;小菜借助三方插件
path_provider來擷取圖檔路徑;
path_provider 提供了 getTemporaryDirectory 臨時路徑 / getApplicationDocumentsDirectory 全局路徑等,可以根據不同的需求存儲不同路徑;
小菜為了測試友善選擇存放在裝置根目錄下 getExternalStorageDirectory;
Future<String> _capturePath(name) async {
Directory documentsDirectory = await getExternalStorageDirectory();
String path = join(documentsDirectory.path, name);
return path;
}
writeAsBytes
檔案的儲存很簡單,直接将 Uint8List 寫入到所在檔案路徑下即可;
File(val).writeAsBytes(unitVal);
但此時存儲或自定義檔案路徑,可能會遇到權限問題,小菜為了測試友善在 Android 中添加讀寫權限,并手動在裝置中打開,之後便可正常存儲;
小菜對檔案存儲還很不熟悉,對于動态申請權限方面也在學習過程中,會在今後的部落格中逐漸整理,如有不對的地方請多多指導!