深入理解Flutter相機插件【Flutter專題22】

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面給大家講解http包和dio包,涉及到一個知識點就是上傳圖片,那麼圖片哪裏來?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個移動設備都帶有一個內置的相機應用程序,用於捕捉圖片、錄製視頻以及一些特定於每個設備的吸引人的功能。但是如果你正在開發一個需要相機訪問的應用程序,那麼你必須自己實現相機功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能會問,當默認的相機應用程序已經可用時,爲什麼我需要再次實現相機功能?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是因爲,如果您想提供適合您的應用的獨特用戶界面,或者添加設備默認相機應用中不存在的功能,那麼它是必需的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,您將學習使用支持 Android 和 iOS 平臺的官方","attrs":{}},{"type":"link","attrs":{"href":"https://pub.dev/packages/camera","title":null,"type":null},"content":[{"type":"text","text":"camera包","attrs":{}}]},{"type":"text","text":"爲 Flutter 應用程序實現基本的相機功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"應用概覽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在深入研究代碼之前,讓我們回顧一下我們將要構建的應用程序。最終的應用程序將包含大部分基本的相機功能,包括:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"捕獲清晰度選擇器","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"變焦控制","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"曝光控制","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閃光模式選擇器","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"翻轉攝像頭的按鈕——後攝像頭到前攝像頭,反之亦然","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用於捕獲圖像的按鈕","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用於從圖像模式切換到視頻模式的切換","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"視頻模式控制——開始、暫停、恢復、停止","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上次捕獲的圖像或視頻預覽","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢索圖像/視頻文件","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看一眼最後的效果圖。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a5/a58326ef14a614b1394ad8128971122c.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"入門","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用以下命令創建一個新的 Flutter 項目:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"flutter create flutter_camera_demo","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用自己喜歡的 IDE 打開項目,但在本示例中,我將使用 VS Code:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"code flutter_camera_demo","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將以下依賴項添加到您的文件中:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pubspec.yaml","attrs":{}}],"attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://pub.dev/packages/camera","title":null,"type":null},"content":[{"type":"text","text":"camera","attrs":{}}]},{"type":"text","text":" : 提供用於實現相機功能的跨平臺 API","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://pub.dev/packages/video_player","title":null,"type":null},"content":[{"type":"text","text":"video_player","attrs":{}}]},{"type":"text","text":":用於預覽捕獲的視頻","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"path_provider","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":":用於將圖像或視頻存儲在目錄中,可以輕鬆訪問它們","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"dependencies: camera: ^0.8.1+7 videoplayer: ^2.1.14 path_provider: ^2.0.2","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用camera如果報錯的話","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"嘗試將 compileSdkVersion 和 targetSdkVersion 更新爲 31。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b4/b470946e3167fb1204e0dc7e07903381.png","alt":"image-20211125150124416","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將文件內容替換爲以下內容:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"main.dart","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import 'package:flutter/material.dart';\n​\nimport 'screens/camera_screen.dart';\n​\nFuture main() async {\n  runApp(MyApp());\n}\n​\nclass MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Flutter Demo',\n      theme: ThemeData(\n        primarySwatch: Colors.blue,\n     ),\n      debugShowCheckedModeBanner: false,\n      home: CameraScreen(),\n   );\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CameraScreen","attrs":{}}],"attrs":{}},{"type":"text","text":"將包含所有的相機功能的代碼與它的用戶界面一起。我們將稍後添加它,但在我們這樣做之前,我們必須在設備上安裝可用的攝像頭。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"檢索可用的相機","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在main.dart文件中,定義一個全局變量,稱爲我們將存儲可用攝像機列表的位置。這將有助於我們以後輕鬆引用它們。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"`cameras","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import 'package:camera/camera.dart';\n​\nList cameras = [];","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以在使用該方法初始化應用程序之前檢索函數內部的相機——只需確保該函數是異步的,因爲它必須等待檢索設備的可用相機,並且通常 Flutter 的函數是一個簡單的函數,只有調用:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"main()``availableCameras()``main()``runApp()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"Future main() async {\n  try {\n    WidgetsFlutterBinding.ensureInitialized();\n    cameras = await availableCameras();\n } on CameraException catch (e) {\n    print('Error in fetching the cameras: $e');\n }\n  runApp(MyApp());\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"初始化相機","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建一個名爲camera_screen.dart的新文件並在其中定義有狀態小部件CameraScreen。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import 'package:camera/camera.dart';\nimport 'package:flutter/material.dart';\n​\nimport '../main.dart';\n​\nclass CameraScreen extends StatefulWidget {\n  @override\n  _CameraScreenState createState() => _CameraScreenState();\n}\n​\nclass _CameraScreenState extends State {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲相機定義一個控制器,爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"isCameraInitialized","attrs":{}}],"attrs":{}},{"type":"text","text":"布爾變量定義一個值,您可以使用它來輕鬆瞭解相機是否已初始化並相應地刷新 UI:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class _CameraScreenState extends State {\n  CameraController? controller;\n  bool _isCameraInitialized = false;\n​\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"控制器將幫助您訪問相機的不同功能,但在使用它們之前,您必須初始化相機。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建一個名爲 的新方法。此方法將有助於處理兩種情況:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"初始化一個新的相機控制器,這是啓動相機屏幕所需要的","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"當用戶翻轉相機視圖或改變相機清晰度時,處置之前的控制器並用具有不同屬性的新控制器替換它","attrs":{}}]}]}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class _CameraScreenState extends State {\n // ...\n\n void onNewCameraSelected(CameraDescription cameraDescription) async {\n final previousCameraController = controller;\n // Instantiating the camera controller\n final CameraController cameraController = CameraController(\n cameraDescription,\n ResolutionPreset.high,\n imageFormatGroup: ImageFormatGroup.jpeg,\n );\n\n // Dispose the previous controller\n await previousCameraController?.dispose();\n\n // Replace with the new controller\n if (mounted) {\n setState(() {\n controller = cameraController;\n });\n }\n\n // Update UI if controller updated\n cameraController.addListener(() {\n if (mounted) setState(() {});\n });\n\n // Initialize controller\n try {\n await cameraController.initialize();\n } on CameraException catch (e) {\n print('Error initializing camera: $e');\n }\n\n // Update the boolean\n if (mounted) {\n setState(() {\n _isCameraInitialized = controller!.value.isInitialized;\n });\n }\n }\n\n @override\n Widget build(BuildContext context) {\n return Scaffold();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在initState()","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"方法內部調用此函數並作爲. 列表的第一個索引通常是設備的後置攝像頭。","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"索引","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":"的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cameras","attrs":{}}],"attrs":{}},{"type":"text","text":"名單-後置攝像頭","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"索引","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cameras","attrs":{}}],"attrs":{}},{"type":"text","text":"名單-前置攝像頭","attrs":{}}]}]}],"attrs":{}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"lass _CameraScreenState extends State {\n // ...\n\n @override\n void initState() {\n onNewCameraSelected(cameras[0]);\n super.initState();\n }\n\n @override\n Widget build(BuildContext context) {\n return Scaffold();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,不要忘記在相機未激活時釋放方法中的內存:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dispose()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"@override\nvoid dispose() {\n controller?.dispose();\n super.dispose();\n}\n ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"處理相機生命週期狀態","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在任何設備上運行相機都被認爲是一項佔用大量內存的任務,因此如何處理釋放內存資源以及何時釋放內存資源非常重要。應用程序的生命週期狀態有助於瞭解狀態變化,以便您作爲開發人員可以做出相應的反應。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Flutter 中,您可以通過添加","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WidgetsBindingObserver","attrs":{}}],"attrs":{}},{"type":"text","text":" mixin 並管理生命週期更改。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"didChangeAppLifecycleState()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class _CameraScreenState extends State\n with WidgetsBindingObserver {\n\n // ...\n\n @override\n void didChangeAppLifecycleState(AppLifecycleState state) {\n final CameraController? cameraController = controller;\n\n // App state changed before we got the chance to initialize.\n if (cameraController == null || !cameraController.value.isInitialized) {\n return;\n }\n\n if (state == AppLifecycleState.inactive) {\n // Free up memory when camera not active\n cameraController.dispose();\n } else if (state == AppLifecycleState.resumed) {\n // Reinitialize the camera with same properties\n onNewCameraSelected(cameraController.description);\n }\n }\n\n @override\n Widget build(BuildContext context) {\n return Scaffold();\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"添加相機預覽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們已經完成了相機狀態的初始化和管理,我們可以定義一個非常基本的用戶界面來預覽相機輸出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 的camera 插件自帶一個方法,調用顯示camera 輸出,用戶界面可以定義如下:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"buildPreview()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class _CameraScreenState extends State\n with WidgetsBindingObserver {\n\n // ...\n\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n body: _isCameraInitialized\n ? AspectRatio(\n aspectRatio: 1 / controller!.value.aspectRatio,\n child: controller!.buildPreview(),\n )\n : Container(),\n );\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"預覽將如下所示:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/70/709840c9f84c464961bda281e75f4d41.png","alt":"相機預覽","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您會注意到設備狀態欄在頂部可見;您可以通過在方法中添加以下內容來隱藏它以防止它阻礙相機視圖:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"initState()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"@override\nvoid initState() {\n // Hide the status bar\n SystemChrome.setEnabledSystemUIOverlays([]);\n\n onNewCameraSelected(cameras[0]);\n super.initState();\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基本的相機預覽已準備就緒!現在,我們可以開始向相機添加功能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"添加清晰度選擇器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ResolutionPreset","attrs":{}}],"attrs":{}},{"type":"text","text":"來定義相機視圖的清晰度。在初始化相機時,我們使用了.","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ResolutionPreset.high","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要更改相機視圖的清晰度,您必須使用新值重新初始化相機控制器。我們將在相機視圖的右上角添加一個下拉菜單,用戶可以在其中選擇分辨率預設。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在類中添加兩個變量,一個用於保存所有","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ResolutionPreset","attrs":{}}],"attrs":{}},{"type":"text","text":"值,另一個用於存儲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"currentResolutionPreset","attrs":{}}],"attrs":{}},{"type":"text","text":"值。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"final resolutionPresets = ResolutionPreset.values;\nResolutionPreset currentResolutionPreset = ResolutionPreset.high;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"修改方法中的相機控制器實例以使用該變量:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()``currentResolutionPreset","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"final CameraController cameraController = CameraController(\n cameraDescription,\n currentResolutionPreset,\n imageFormatGroup: ImageFormatGroup.jpeg,\n);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"DropdownButton","attrs":{}}],"attrs":{}},{"type":"text","text":"可被定義爲如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"DropdownButton(\n dropdownColor: Colors.black87,\n underline: Container(),\n value: currentResolutionPreset,\n items: [\n for (ResolutionPreset preset\n in resolutionPresets)\n DropdownMenuItem(\n child: Text(\n preset\n .toString()\n .split('.')[1]\n .toUpperCase(),\n style:\n TextStyle(color: Colors.white),\n ),\n value: preset,\n )\n ],\n onChanged: (value) {\n setState(() {\n currentResolutionPreset = value!;\n _isCameraInitialized = false;\n });\n onNewCameraSelected(controller!.description);\n },\n hint: Text(\"Select item\"),\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用該方法以使用新的清晰度值重新初始化相機控制器。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"錄製的圖片有點大,大家下載預覽吧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-quality-selector-demo.gif","title":"","type":null},"content":[{"type":"text","text":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-quality-selector-demo.gif","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"變焦控制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用控制器上的方法並傳遞縮放值來設置相機的縮放級別。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setZoomLevel()","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在確定縮放級別之前,您應該知道設備相機的最小和最大縮放級別。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義三個變量:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"double _minAvailableZoom = 1.0;\ndouble _maxAvailableZoom = 1.0;\ndouble _currentZoomLevel = 1.0;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"檢索這些值的最佳位置是在相機初始化後的方法內部。您可以使用以下方法獲得最小和最大縮放級別:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"cameraController\n .getMaxZoomLevel()\n .then((value) => _maxAvailableZoom = value);\n\ncameraController\n .getMinZoomLevel()\n .then((value) => _minAvailableZoom = value);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以實現一個滑塊,讓用戶選擇合適的縮放級別;構建的代碼","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Slider","attrs":{}}],"attrs":{}},{"type":"text","text":"如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Row(\n children: [\n Expanded(\n child: Slider(\n value: _currentZoomLevel,\n min: _minAvailableZoom,\n max: _maxAvailableZoom,\n activeColor: Colors.white,\n inactiveColor: Colors.white30,\n onChanged: (value) async {\n setState(() {\n _currentZoomLevel = value;\n });\n await controller!.setZoomLevel(value);\n },\n ),\n ),\n Container(\n decoration: BoxDecoration(\n color: Colors.black87,\n borderRadius: BorderRadius.circular(10.0),\n ),\n child: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Text(\n _currentZoomLevel.toStringAsFixed(1) +\n 'x',\n style: TextStyle(color: Colors.white),\n ),\n ),\n ),\n ],\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每次拖動滑塊時,都會調用該方法來更新縮放級別值。在上面的代碼中,我們還添加了一個小部件來顯示當前的縮放級別值。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setZoomLevel()``Text","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"錄製的圖片有點大,大家下載預覽吧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-zoom-demo.gif","title":"","type":null},"content":[{"type":"text","text":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-zoom-demo.gif","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"曝光控制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用控制器上的方法並傳遞曝光值來設置相機的曝光偏移值。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setExposureOffset()","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,讓我們檢索設備支持的相機曝光的最小值和最大值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義三個變量:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"ouble _minAvailableExposureOffset = 0.0;\ndouble _maxAvailableExposureOffset = 0.0;\ndouble _currentExposureOffset = 0.0;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取方法內的最小和最大相機曝光值:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"cameraController\n .getMinExposureOffset()\n .then((value) => _minAvailableExposureOffset = value);\n\ncameraController\n .getMaxExposureOffset()\n .then((value) => _maxAvailableExposureOffset = value);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們將構建一個用於顯示和控制曝光偏移的垂直滑塊。Material Design 不提供垂直","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Slider","attrs":{}}],"attrs":{}},{"type":"text","text":"小部件,但您可以使用四分之三圈的","attrs":{}},{"type":"link","attrs":{"href":"https://api.flutter.dev/flutter/widgets/RotatedBox-class.html","title":null,"type":null},"content":[{"type":"text","text":"RotatedBox類","attrs":{}}]},{"type":"text","text":"來實現這一點。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Container(\n decoration: BoxDecoration(\n color: Colors.white,\n borderRadius: BorderRadius.circular(10.0),\n ),\n child: Padding(\n padding: const EdgeInsets.all(8.0),\n child: Text(\n _currentExposureOffset.toStringAsFixed(1) + 'x',\n style: TextStyle(color: Colors.black),\n ),\n ),\n),\nExpanded(\n child: RotatedBox(\n quarterTurns: 3,\n child: Container(\n height: 30,\n child: Slider(\n value: _currentExposureOffset,\n min: _minAvailableExposureOffset,\n max: _maxAvailableExposureOffset,\n activeColor: Colors.white,\n inactiveColor: Colors.white30,\n onChanged: (value) async {\n setState(() {\n _currentExposureOffset = value;\n });\n await controller!.setExposureOffset(value);\n },\n ),\n ),\n ),\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的代碼中,我們","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Text","attrs":{}}],"attrs":{}},{"type":"text","text":"在滑塊頂部構建了一個小部件來顯示當前的曝光偏移值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"錄製的圖片有點大,大家下載預覽吧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-exposure-offset-demo.gif)","title":"","type":null},"content":[{"type":"text","text":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-exposure-offset-demo.gif)","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"閃光模式選擇器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用該方法並傳遞一個值來設置相機的閃光模式。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setFlashMode()``FlashMode","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個變量來存儲 flash 模式的當前值:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"FlashMode? _currentFlashMode;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後獲取方法內部的初始 flash 模式值:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"_currentFlashMode = controller!.value.flashMode;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在用戶界面上,我們將連續顯示可用的閃光模式,用戶可以點擊其中任何一種來選擇該閃光模式。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Row(\n mainAxisAlignment: MainAxisAlignment.spaceBetween,\n children: [\n InkWell(\n onTap: () async {\n setState(() {\n _currentFlashMode = FlashMode.off;\n });\n await controller!.setFlashMode(\n FlashMode.off,\n );\n },\n child: Icon(\n Icons.flash_off,\n color: _currentFlashMode == FlashMode.off\n ? Colors.amber\n : Colors.white,\n ),\n ),\n InkWell(\n onTap: () async {\n setState(() {\n _currentFlashMode = FlashMode.auto;\n });\n await controller!.setFlashMode(\n FlashMode.auto,\n );\n },\n child: Icon(\n Icons.flash_auto,\n color: _currentFlashMode == FlashMode.auto\n ? Colors.amber\n : Colors.white,\n ),\n ),\n InkWell(\n onTap: () async {\n setState(() {\n _isCameraInitialized = false;\n });\n onNewCameraSelected(\n cameras[_isRearCameraSelected ? 1 : 0],\n );\n setState(() {\n _isRearCameraSelected = !_isRearCameraSelected;\n });\n },\n child: Icon(\n Icons.flash_on,\n color: _currentFlashMode == FlashMode.always\n ? Colors.amber\n : Colors.white,\n ),\n ),\n InkWell(\n onTap: () async {\n setState(() {\n _currentFlashMode = FlashMode.torch;\n });\n await controller!.setFlashMode(\n FlashMode.torch,\n );\n },\n child: Icon(\n Icons.highlight,\n color: _currentFlashMode == FlashMode.torch\n ? Colors.amber\n : Colors.white,\n ),\n ),\n ],\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選定的閃光模式將以琥珀色而不是白色突出顯示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7d/7d2d21f0b941f65a38fc207beeaa3051.gif","alt":"演示相機閃光燈選擇器的 gif","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"翻轉相機切換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要在前後攝像頭之間切換,您必須通過向方法提供新值來重新初始化攝像頭。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"onNewCameraSelected()","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個布爾變量來了解是否選擇了後置攝像頭,否則選擇了前置攝像頭。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"bool _isRearCameraSelected = true;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以前,我們使用後置攝像頭進行初始化,因此我們將存儲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":"在此布爾值中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,我們將顯示一個按鈕來在後置攝像頭和前置攝像頭之間切換:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"InkWell(\n onTap: () {\n setState(() {\n _isCameraInitialized = false;\n });\n onNewCameraSelected(\n cameras[_isRearCameraSelected ? 0 : 1],\n );\n setState(() {\n _isRearCameraSelected = !_isRearCameraSelected;\n });\n },\n child: Stack(\n alignment: Alignment.center,\n children: [\n Icon(\n Icons.circle,\n color: Colors.black38,\n size: 60,\n ),\n Icon(\n _isRearCameraSelected\n ? Icons.camera_front\n : Icons.camera_rear,\n color: Colors.white,\n size: 30,\n ),\n ],\n ),\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的代碼中,如果","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_isRearCameraSelected","attrs":{}}],"attrs":{}},{"type":"text","text":"布爾值爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":",則","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":"作爲索引傳遞給","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cameras","attrs":{}}],"attrs":{}},{"type":"text","text":"(翻轉到前置攝像頭),否則","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"作爲索引傳遞(翻轉到後置攝像頭)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"錄製的圖片有點大,大家下載預覽吧","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-exposure-offset-demo.gif","title":"","type":null},"content":[{"type":"text","text":"https://luckly007.oss-cn-beijing.aliyuncs.com/image/camera-exposure-offset-demo.gif","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"捕捉圖像","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以使用相機控制器上的方法使用設備相機拍照。捕獲的圖片作爲a (這是一個跨平臺的文件抽象)返回。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"takePicture()``XFile","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們定義一個函數來處理圖片的捕獲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future takePicture() async {\n final CameraController? cameraController = controller;\n if (cameraController!.value.isTakingPicture) {\n // A capture is already pending, do nothing.\n return null;\n }\n try {\n XFile file = await cameraController.takePicture();\n return file;\n } on CameraException catch (e) {\n print('Error occured while taking picture: $e');\n return null;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該函數返回捕獲的圖片,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"XFile","attrs":{}}],"attrs":{}},{"type":"text","text":"如果捕獲成功,則返回","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"null","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"捕獲按鈕可以定義如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"InkWell(\n onTap: () async {\n XFile? rawImage = await takePicture();\n File imageFile = File(rawImage!.path);\n\n int currentUnix = DateTime.now().millisecondsSinceEpoch;\n final directory = await getApplicationDocumentsDirectory();\n String fileFormat = imageFile.path.split('.').last;\n\n await imageFile.copy(\n '${directory.path}/$currentUnix.$fileFormat',\n );\n },\n child: Stack(\n alignment: Alignment.center,\n children: [\n Icon(Icons.circle, color: Colors.white38, size: 80),\n Icon(Icons.circle, color: Colors.white, size: 65),\n ],\n ),\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"捕獲成功後,會將圖片保存到應用程序的文檔目錄中,並以時間戳作爲圖片名稱,以便以後可以輕鬆訪問所有捕獲的圖片。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"在圖像和視頻模式之間切換","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"TextButton","attrs":{}}],"attrs":{}},{"type":"text","text":"連續使用兩個s 在圖像和視頻模式之間切換。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個布爾變量來存儲所選模式:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"bool _isVideoCameraSelected = false;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"UI 按鈕可以這樣定義:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Row(\n children: [\n Expanded(\n child: Padding(\n padding: const EdgeInsets.only(\n left: 8.0,\n right: 4.0,\n ),\n child: TextButton(\n onPressed: _isRecordingInProgress\n ? null\n : () {\n if (_isVideoCameraSelected) {\n setState(() {\n _isVideoCameraSelected = false;\n });\n }\n },\n style: TextButton.styleFrom(\n primary: _isVideoCameraSelected\n ? Colors.black54\n : Colors.black,\n backgroundColor: _isVideoCameraSelected\n ? Colors.white30\n : Colors.white,\n ),\n child: Text('IMAGE'),\n ),\n ),\n ),\n Expanded(\n child: Padding(\n padding: const EdgeInsets.only(\n left: 4.0, right: 8.0),\n child: TextButton(\n onPressed: () {\n if (!_isVideoCameraSelected) {\n setState(() {\n _isVideoCameraSelected = true;\n });\n }\n },\n style: TextButton.styleFrom(\n primary: _isVideoCameraSelected\n ? Colors.black\n : Colors.black54,\n backgroundColor: _isVideoCameraSelected\n ? Colors.white\n : Colors.white30,\n ),\n child: Text('VIDEO'),\n ),\n ),\n ),\n ],\n)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e3/e3451909a4fda37058ecf907524b2f47.gif","alt":"演示圖像和視頻模式的相機切換的 gif","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"視頻錄製","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要使用設備攝像頭管理視頻錄製,您必須定義四個函數來處理錄製過程的狀態:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"startVideoRecording()","attrs":{}}],"attrs":{}},{"type":"text","text":" 開始視頻錄製過程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"stopVideoRecording()","attrs":{}}],"attrs":{}},{"type":"text","text":" 停止視頻錄製過程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"pauseVideoRecording()","attrs":{}}],"attrs":{}},{"type":"text","text":" 暫停錄製,如果它已經在進行中","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"resumeVideoRecording()","attrs":{}}],"attrs":{}},{"type":"text","text":" 如果處於暫停狀態,則恢復錄製","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,定義一個布爾變量來存儲是否正在進行錄製:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"bool _isRecordingInProgress = false;","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"開始錄製","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以通過調用相機控制器上的方法開始視頻錄製:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"startVideoRecording()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future startVideoRecording() async {\n final CameraController? cameraController = controller;\n if (controller!.value.isRecordingVideo) {\n // A recording has already started, do nothing.\n return;\n }\n try {\n await cameraController!.startVideoRecording();\n setState(() {\n _isRecordingInProgress = true;\n print(_isRecordingInProgress);\n });\n } on CameraException catch (e) {\n print('Error starting to record video: $e');\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開始錄製後,布爾值","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_isRecordingInProgress","attrs":{}}],"attrs":{}},{"type":"text","text":"設置爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"停止錄製","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以通過調用控制器上的方法來停止已經在進行的視頻錄製:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"stopVideoRecording()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future stopVideoRecording() async {\n if (!controller!.value.isRecordingVideo) {\n // Recording is already is stopped state\n return null;\n }\n try {\n XFile file = await controller!.stopVideoRecording();\n setState(() {\n _isRecordingInProgress = false;\n print(_isRecordingInProgress);\n });\n return file;\n } on CameraException catch (e) {\n print('Error stopping video recording: $e');\n return null;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"錄製停止後,布爾值","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_isRecordingInProgress","attrs":{}}],"attrs":{}},{"type":"text","text":"設置爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"false","attrs":{}}],"attrs":{}},{"type":"text","text":"。該方法以格式返回視頻文件。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"stopVideoRecording()``XFile","attrs":{}}],"attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"暫停錄製","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以通過調用控制器上的方法暫停正在進行的視頻錄製:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pauseVideoRecording()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future pauseVideoRecording() async {\n if (!controller!.value.isRecordingVideo) {\n // Video recording is not in progress\n return;\n }\n try {\n await controller!.pauseVideoRecording();\n } on CameraException catch (e) {\n print('Error pausing video recording: $e');\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"恢復錄製","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以通過調用控制器上的方法來恢復暫停的視頻錄製:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"resumeVideoRecording()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future resumeVideoRecording() async {\n if (!controller!.value.isRecordingVideo) {\n // No video recording was in progress\n return;\n }\n try {\n await controller!.resumeVideoRecording();\n } on CameraException catch (e) {\n print('Error resuming video recording: $e');\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"開始和停止錄製的按鈕","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您可以通過檢查","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_isVideoCameraSelected","attrs":{}}],"attrs":{}},{"type":"text","text":"布爾值是否爲真並在該位置顯示視頻開始/停止按鈕來修改拍照按鈕。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"InkWell(\n onTap: _isVideoCameraSelected\n ? () async {\n if (_isRecordingInProgress) {\n XFile? rawVideo = await stopVideoRecording();\n File videoFile = File(rawVideo!.path);\n\n int currentUnix = DateTime.now().millisecondsSinceEpoch;\n\n final directory = await getApplicationDocumentsDirectory();\n String fileFormat = videoFile.path.split('.').last;\n\n _videoFile = await videoFile.copy(\n '${directory.path}/$currentUnix.$fileFormat',\n );\n\n _startVideoPlayer();\n } else {\n await startVideoRecording();\n }\n }\n : () async {\n // code to handle image clicking\n },\n child: Stack(\n alignment: Alignment.center,\n children: [\n Icon(\n Icons.circle,\n color: _isVideoCameraSelected\n ? Colors.white\n : Colors.white38,\n size: 80,\n ),\n Icon(\n Icons.circle,\n color: _isVideoCameraSelected\n ? Colors.red\n : Colors.white,\n size: 65,\n ),\n _isVideoCameraSelected &&\n _isRecordingInProgress\n ? Icon(\n Icons.stop_rounded,\n color: Colors.white,\n size: 32,\n )\n : Container(),\n ],\n ),\n) ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣,在錄製過程中,您可以檢查布爾值是否","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_isRecordingInProgress","attrs":{}}],"attrs":{}},{"type":"text","text":"爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"true","attrs":{}}],"attrs":{}},{"type":"text","text":"並顯示暫停/恢復按鈕而不是相機翻轉按鈕。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"上次捕獲的預覽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們在相機視圖的右下角顯示最後拍攝的圖片或錄製的視頻的預覽。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了實現這一點,我們還必須定義一種視頻播放方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義一個視頻播放器控制器:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"VideoPlayerController? videoController;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下方法用於使用存儲在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"_videoFile","attrs":{}}],"attrs":{}},{"type":"text","text":"變量中的視頻文件啓動視頻播放器:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Future _startVideoPlayer() async {\n if (_videoFile != null) {\n videoController = VideoPlayerController.file(_videoFile!);\n await videoController!.initialize().then((_) {\n // Ensure the first frame is shown after the video is initialized,\n // even before the play button has been pressed.\n setState(() {});\n });\n await videoController!.setLooping(true);\n await videoController!.play();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,不要忘記釋放方法中的內存:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"dispose()","attrs":{}}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"@override\nvoid dispose() {\n // ...\n videoController?.dispose();\n super.dispose();\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"預覽的用戶界面可以定義如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Container(\n width: 60,\n height: 60,\n decoration: BoxDecoration(\n color: Colors.black,\n borderRadius: BorderRadius.circular(10.0),\n border: Border.all(color: Colors.white, width: 2),\n image: _imageFile != null\n ? DecorationImage(\n image: FileImage(_imageFile!),\n fit: BoxFit.cover,\n )\n : null,\n ),\n child: videoController != null && videoController!.value.isInitialized\n ? ClipRRect(\n borderRadius: BorderRadius.circular(8.0),\n child: AspectRatio(\n aspectRatio: videoController!.value.aspectRatio,\n child: VideoPlayer(videoController!),\n ),\n )\n : Container(),\n)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"檢索圖像/視頻文件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於我們已將所有捕獲的圖像和錄製的視頻存儲在應用程序文檔目錄的單個文件夾中,因此您可以輕鬆檢索所有文件。如果您想在畫廊視圖中顯示它們,或者您只想在預覽中顯示最後捕獲的圖像或視頻文件的縮略圖,這可能是必要的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們將定義一個方法,當新的捕獲或錄製完成時,該方法也將刷新預覽圖像/視頻。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"// To store the retrieved files\nList allFileList = [];\n\nrefreshAlreadyCapturedImages() async {\n // Get the directory\n final directory = await getApplicationDocumentsDirectory();\n List fileList = await directory.list().toList();\n allFileList.clear();\n\n List> fileNames = [];\n\n // Searching for all the image and video files using \n // their default format, and storing them\n fileList.forEach((file) {\n if (file.path.contains('.jpg') || file.path.contains('.mp4')) {\n allFileList.add(File(file.path));\n\n String name = file.path.split('/').last.split('.').first;\n fileNames.add({0: int.parse(name), 1: file.path.split('/').last});\n }\n });\n\n // Retrieving the recent file\n if (fileNames.isNotEmpty) {\n final recentFile =\n fileNames.reduce((curr, next) => curr[0] > next[0] ? curr : next);\n String recentFileName = recentFile[1];\n // Checking whether it is an image or a video file\n if (recentFileName.contains('.mp4')) {\n _videoFile = File('${directory.path}/$recentFileName');\n _startVideoPlayer();\n } else {\n _imageFile = File('${directory.path}/$recentFileName');\n }\n\n setState(() {});\n }\n}","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"您已經創建了一個具有所有基本功能的成熟相機應用程序。您現在甚至可以向此應用程序添加自定義功能,並自定義用戶界面以匹配您應用程序的設計調色板。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章