Android Ios Flutter

Flutter跨平台開發-拍照及儲存圖片(申請權限~功能實作)

吳柏陞 Paul Wu 2023/06/30 11:41:46
1305

app開發過程中,總有許多功能需要先向用戶申請權限,待用戶同意後方可使用,

例如:相機、存取手機內部資源、麥克風、定位...等等。

即便使用Flutter開發,也免不了對這部分該下的工。

本篇將介紹如何使用Flutter實做相機的拍照功能,並在完成拍照後將照片顯示於次頁,且在次頁中新增一個儲存圖片的按鈕,使得拍下的照片能夠保存於手機內的圖片庫中。

包含使用到的套件與權限、申請權限的方式、camera套件使用、gallery_saver套件使用,以及成果Demo。

Flutter相關文章可參考以下:

安裝與環境建置套件管理以及pubspec.yaml設定Flutter頁面切換

步驟

1.前置作業

2.添加進入拍照頁(CameraPage)的按鈕

3.建立拍照頁(CameraPage)

4.建立顯示頁(DisplayPictureScreen)

1.前置作業

1.1 pubspec.yaml中加入需使用到的套件

permission_handler:向用戶申請權限。

camera:調用相機以及使用相機拍照功能。

gallery_saver:將圖片儲存至相簿。

1.2 在AndroidManifest.xml中加入所需權限

本篇雖只介紹到拍照功能,但使用camera套件調用相機時需要用戶同意相機權限(CAMERA)以及麥克風權限(RECORD_AUDIO)。

檔案存取權限(WRITE_EXTERNAL_STORAGE、READ_EXTERNAL_STORAGE)

1.3 獲取可用相機

在runApp之前,使用camera套件獲取可用相機列表。

Future<void> main() async {
  //確保套件已初始化
  WidgetsFlutterBinding.ensureInitialized();
  //獲取裝置中可用的相機列表。
  final cameras = await availableCameras();
  //從列表中取第一個可用相機。
  final firstCamera = cameras.first;

  runApp(MyApp(
    camera: firstCamera,
  ));
}

2.添加進入拍照頁(CameraPage)的按鈕

2.1 申請權限

此處向用戶申請相機、存取與麥克風權限。

isGranted為同意,isPermanentlyDenied為拒絕且不再詢問,denied為拒絕

  Future<PermissionStatus> getPermissions() async {
    PermissionStatus permissionStatus = PermissionStatus.granted;
    Map<Permission, PermissionStatus> status =
        await [Permission.camera, Permission.storage, Permission.microphone].request();

    List<PermissionStatus> statusList = [];
    if (status[Permission.camera] != null) {
      statusList.add(status[Permission.camera]!);
    }
    if (status[Permission.storage] != null) {
      statusList.add(status[Permission.storage]!);
    }
    if (status[Permission.microphone] != null) {
      statusList.add(status[Permission.microphone]!);
    }
    statusList.forEach((element) {
      if(element.isGranted){
        print("同意");
      }else if(element.isPermanentlyDenied){
        print("拒絕且不再提醒");
        permissionStatus = PermissionStatus.permanentlyDenied;
      }else{
        print("拒絕");
        if(permissionStatus == PermissionStatus.granted){
          permissionStatus = PermissionStatus.denied;
        }
      }
    });

    return permissionStatus;
  }

2.2 建立按鈕並執行權限申請與判斷

建立一個按鈕並於onPressed設定點擊事件,執行getPermissions向用戶申請權限。

child: IconButton(
...
  onPressed: () {
    //權限申請
    getPermissions().then((value){
      if(value.isGranted){
        //成功取得權限,換頁至拍照頁(CameraPage)
        Navigator.of(context).push(
            MaterialPageRoute(
                builder: (context) =>
                    CameraPage(
                        camera: widget.camera)));
      }else{
        Fluttertoast.showToast(
          msg: "權限取得失敗",
        );
      }
    });
  },
),

執行結果:點下拍照按鈕後,將會向用戶申請權限。

2.3 拒絕且不再詢問

依照目前的寫法,當用戶勾選不再詢問且拒絕時,再點擊拍照紐就只會出現氣泡訊息告知用戶權限取得失敗。

此時我們需要調整寫法,新增一個判斷,當用戶勾選不再詢問且拒絕時,將執行showAlertDialog提示用戶,需前往設定頁打開權限。

if(value.isGranted){
...
}else{
  Fluttertoast.showToast(
    msg: "權限取得失敗",
  );
  //拒絕且不再詢問
  if(value.isPermanentlyDenied){
    //提醒用戶打開權限
    showAlertDialog(context);
  }
}

當用戶點下確認時,會執行 openAppSettings將用戶導向設定頁。

執行結果:用戶勾選不再詢問且拒絕時,將跳出提示訊息,點下確定後會跳轉至設定頁。


3.建立拍照頁(CameraPage)

3.1 創建相機控制器(CameraController)

使用camera套件之功能,創建相機控制器初始化相機所返回的 Future

initState時初始化相機。

  @override
  void initState() {
    super.initState();
    //相機控制器
    _controller = CameraController(
      //需將1.3獲取的可用相機帶入此頁做使用
      widget.camera,
      //設定相機拍攝的質量,medium為480p
      ResolutionPreset.medium,
    );
    //初始化相機控制器
    _initializeControllerFuture = _controller.initialize();
  }

注意:在dispose()時需销毁相機控制器。

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

3.2 建立執行拍照的按鈕

await _initializeControllerFuture;//確保相機已初始化

final image = await _controller.takePicture();//執行拍照動作,完成後會將圖片存於暫存中,並將暫存路徑寫入image。

這時手機的圖片庫還不到此圖片,此處將路徑帶入下一頁(DisplayPictureScreen)做顯示,

      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          try {
            await _initializeControllerFuture;
            //執行拍照動作
            final image = await _controller.takePicture();
            if (!mounted) return;
            //動作完成後,將照片的路徑傳入DisplayPictureScreen頁。
            await Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(
                  imagePath: image.path,
                ),
              ),
            );
          } catch (e) {
            print(e);
          }
        },
        child: const Icon(Icons.camera_alt),
      ),

執行結果:點下拍照按鈕後,將會擷取當下畫面,並帶入下一頁。

4.建立顯示頁(DisplayPictureScreen)

在body中放入前頁取得的路徑,顯示前頁拍下並暫存的圖片。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Display the Picture')),
      //前頁取得之路徑
      body: Image.file(File(imagePath)),
      ...
    );
  }

建立一個按鈕,使用gallery_saver套件之功能執行圖片儲存,

此處無論成功或失敗都會顯示Toast訊息,提醒用戶是否成功儲存。

      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          //儲存相片至相簿
          GallerySaver.saveImage(imagePath).then((value) {
            if (value == true) {
              Fluttertoast.showToast(
                msg: "照片已儲存至相簿",
              );
            }else{
              Fluttertoast.showToast(
                msg: "儲存失敗",
              );
            }
          });
        },
        child: const Icon(Icons.download),
      ),

執行結果:點下按鈕後,下方將有氣泡訊息提示用戶。

此時再到手機內的圖片庫,即可看見剛才拍攝的照片囉。

結語

由於安全性的問題,如今使用APP時有許多功能都需先向用戶端取得權限,

申請權限的方式,以及詢問的時機點,就顯得相當重要了,

還有error handle的處理,當用戶拒絕了權限申請又選擇不再詢問時,

總不能讓用戶停在當下畫面,這些都是開發者需要為用戶想到的。

吳柏陞 Paul Wu