---
# System prepended metadata

title: 下載組件截圖
tags: [Flutter]

---

# 下載組件截圖

不是全螢幕截圖，<br/>
而是要把特定範圍的組件存成圖片。<br/>
類似 web 端的 html2canvas<br/>
要完成這件事需要三個步驟：
1. 用 `RepaintBoundary` 包圍想擷取成圖片的組件，並綁定一個 `GlobalKey`
2. `Widget` 轉 `Unit8List`
3. 下載 `Uint8List` 至裝置的圖庫

### Widget 轉 Uint8List

傳入之 key 為 flutter 的 `GlobalKey`，方便我們像是操作 DOM 一樣可以指到想截圖的 `RepaintBoundary` 組件。

##### 程式碼

```dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;

Future<Uint8List> captureImage(
  GlobalKey key, [
  double pixelRatio = 3.0,
  ui.ImageByteFormat format = ui.ImageByteFormat.png,
]) async {
  try {
    RenderRepaintBoundary boundary =
      key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
    // Issue: https://github.com/flutter/flutter/issues/22308
    await Future.delayed(const Duration(milliseconds: 200));
    ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
    ByteData? byteData = await image.toByteData(format: format);
    final bytes = byteData!.buffer.asUint8List();
    return bytes;
  } catch (e) {
    throw e;
  }
}
```

### 下載 Uint8List 至裝置的圖庫

要寫入剛剛轉換成 unit8List 到圖庫，<br>
我們僅需要使用 `image_gallery_saver` 這個插件並補上兩個平台對應所需要的權限字串即可。

##### 權限

###### Android

AndroidManifest.xml

```xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application android:requestLegacyExternalStorage="true" ...>...</application>
```

###### iOS

Info.plist

```plist
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app requires permission to save images to gallery.</string>
```

##### 程式碼

```dart
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:permission_handler/permission_handler.dart';

Future<bool> saveImage(
  bytes,
  String name, {
  quality = 60,
}) async {
  try {
    final isGranted = await Permission.storage.request().isGranted;
    if (!isGranted) return false;
    final res = await ImageGallerySaver.saveImage(
      bytes,
      quality: quality,
      name: name,
    );
    return res['isSuccess'];
  } catch (e) {
    throw e;
  }
}
```

### 在組件中使用

在想擷取的組件外包一個 `RepaintBoundary`，並設定一個 `GlobalKey`。

##### 程式碼

```dart
import 'package:flutter/material.dart';
import 'package:cadddy/utils/image.dart';

class Example extends StatefulWidget {
  const Example({ Key? key }): super(key: key);
  
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  const _ExampleState({
    Key? key,
  }): super(key: key);
  final _globalKey = new GlobalKey();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          child: RepaintBoundary(
            key: _globalKey,
            // 這邊裡面的 Widget 就是你想擷取的部分
            child: Text('邀请卡'),
          ),
        ),
      ),
      floatActionButton: FloatActionButton(
        onPressed: () async {
          final bytes = await captureImage(_globalKey);
          final res = await saveImage(bytes, '邀请卡.png');
          print('下载成功');
        }
      ),
    );
  }
}
```

##### 成果

![download button](https://i.imgur.com/I7j5LOH.png =200x) ![download image success](https://i.imgur.com/Q34riZM.png =200x)

### 參考
- [Export your widget to image with flutter](https://medium.com/flutter-community/export-your-widget-to-image-with-flutter-dc7ecfa6bafb)
- [image_gallery_saver](https://pub.dev/packages/image_gallery_saver)

###### tags: `Flutter`
