# Flutter 好用package - camera 介紹 ## camera 使用手機內建相機功能 ``` flutter pub add camera ``` [官網](https://pub.dev/packages/camera) ### 程式範例 #### 畫面 ```dart class CaptureImagePage extends StatefulWidget { static MaterialPage page() { return MaterialPage( name: APPPages.captureImagePage, key: ValueKey(APPPages.captureImagePage), child: const CaptureImagePage()); } const CaptureImagePage({Key? key}) : super(key: key); @override State<SCaptureImagePage> createState() { return _CaptureImagePageState(); } } class _CaptureImagePageState extends State<CaptureImagePage> with WidgetsBindingObserver, TickerProviderStateMixin { @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); Provider.of<CameraManager>(context, listen: false).init(); } @override void deactivate() { super.deactivate(); Provider.of<CameraManager>(context, listen: false).reset(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = Provider.of<CameraManager>(context, listen: false).controller; if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); Provider.of<CameraManager>(context, listen: false).isInit = false; } else if (state == AppLifecycleState.resumed) { Provider.of<CameraManager>(context, listen: false) .onNewCameraSelected(cameraDescription: cameraController.description); } } @override Widget build(BuildContext context) { return BasicPage( safeArea: false, widget: Provider.of<CameraManager>(context, listen: true).isInit ? Stack( children: [ Transform.scale( scale: 1 / (Provider.of<CameraManager>(context, listen: false) .controller! .value .aspectRatio * MediaQuery.of(context).size.aspectRatio), alignment: Alignment.topCenter, child: CameraPreview( Provider.of<CameraManager>(context, listen: false) .controller!, ), ), const Align( alignment: Alignment.bottomCenter, child: MainFunBtns()), const CaptureImageMessage() ], ) : const Center(child: CircularProgressIndicator())); } } class MainFunBtn extends StatelessWidget { const MainFunBtn( {super.key, required this.text, required this.filePath, this.onTap}); final String text; final String filePath; final Function()? onTap; @override Widget build(BuildContext context) { return CircleImageBtn( text: text, fontSize: 16, textColor: Colors.white, onTap: onTap, height: 70, width: 70, filePath: filePath, ); } } class MainFunBtns extends StatelessWidget { const MainFunBtns({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 16, left: 16, bottom: 34), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MainFunBtn( text: '拍照', onTap: () { Provider.of<CameraManager>(context, listen: false) .onTakePictureButtonPressed( account: LocalStorage.get('account')); }, filePath: 'assets/images/self_medication_capture_image_page/icon_dslr-camera.png', ), MainFunBtn( text: '鏡頭反轉', onTap: () { Provider.of<CameraManager>(context, listen: false) .onNewCameraSelected(); }, filePath: 'assets/images/self_medication_capture_image_page/icon_flip_lens.png', ), ], ), ); } } class GalleryBtn extends StatelessWidget { const GalleryBtn({super.key}); @override Widget build(BuildContext context) { return Stack( children: [ MainFunBtn( text: '檢視照片', onTap: () { Provider.of<AppStateManager>(context, listen: false) .showSelfMedicationScreenshotListPage(); }, filePath: 'assets/images/self_medication_capture_image_page/icon_album.png', ), const GalleryNum() ], ); } } class CaptureImageMessage extends StatelessWidget { const CaptureImageMessage({super.key}); @override Widget build(BuildContext context) { return Consumer<CameraManager>(builder: (context, manager, child) { return CaptureImage( isCaptureFrame: manager.isCaptureImage, text: '已拍攝照片', ); }); } } ``` #### 狀態管理 camera_manager.dart ```dart import 'dart:async'; import 'package:camera/camera.dart'; import 'package:catcatcat/enums/file_type.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../helper/fileHelper.dart'; import '../../utils/fake_encryption.dart'; class CameraManager extends ChangeNotifier { List<CameraDescription> _cameras = <CameraDescription>[]; CameraController? _controller; bool isInit = false; //相機是否初始化完成 bool _isFrontCamera = true; //是否使用前鏡頭 bool _isCaptureImage = false; //拍攝照片動畫用 List<CameraDescription> get cameras => _cameras; CameraController? get controller => _controller; bool get isCaptureImage => _isCaptureImage; //取得所有可以使用相機,通常[0]:後鏡頭 [1]:前鏡頭 Future<void> getAvailableCameras() async { _cameras = await availableCameras(); } //將相機初始化 void init() { _controller = CameraController(_cameras.first, ResolutionPreset.medium, imageFormatGroup: ImageFormatGroup.jpeg); _controller!.initialize().then((value) { isInit = true; notifyListeners(); }); _isFrontCamera = true; } //重置回原始狀況 void reset() { isInit = false; } //鏡頭切換 Future<void> onNewCameraSelected( {CameraDescription? cameraDescription}) async { if (cameraDescription == null) { if (_isFrontCamera) { cameraDescription = _cameras[1]; } else { cameraDescription = _cameras[0]; } _isFrontCamera = !_isFrontCamera; } final CameraController? oldController = _controller; if (oldController != null) { _controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, ResolutionPreset.medium, enableAudio: false, imageFormatGroup: ImageFormatGroup.jpeg, ); _controller = cameraController; cameraController.addListener(() { if (cameraController.value.hasError) { debugPrint('Camera error ${cameraController.value.errorDescription}'); } }); try { await cameraController.initialize(); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': debugPrint('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only debugPrint('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only debugPrint('Camera access is restricted.'); break; case 'AudioAccessDenied': debugPrint('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only debugPrint('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only debugPrint('Audio access is restricted.'); break; default: debugPrint('Error: ${e.code}\n${e.description}'); break; } } notifyListeners(); } //拍照 void onTakePictureButtonPressed({required String account}) { if (!_isCaptureImage) { _isCaptureImage = true; notifyListeners(); _takePicture().then((XFile? file) async { DateTime now = DateTime.now().toUtc(); String formattedDate = DateFormat('yyyyMMddkkmmss').format(now); String storagePath = await FileHelper.getFilePath( account: account, fileType: FileType.selfMedication); if (file != null) { FakeEncryption.moveAndEncodeFile( filePath: '$storagePath$formattedDate.jpg', oriFilePath: file.path, ); Timer(const Duration(milliseconds: 1000), () { _isCaptureImage = false; notifyListeners(); }); } }); } } //呼叫camera套件拍照功能 Future<XFile?> _takePicture() async { final CameraController? cameraController = _controller; if (cameraController == null || !cameraController.value.isInitialized) { debugPrint('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { debugPrint('Error: ${e.code}\n${e.description}'); return null; } } } ``` ###### tags: `flutter`