Flutter跨平台開發-拍照及儲存圖片(申請權限~功能實作)
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的處理,當用戶拒絕了權限申請又選擇不再詢問時,
總不能讓用戶僅僅停在當下畫面,這些都是開發者需要為用戶想到的。