很多应用都需要使用到设备的相机模块拍摄图片和视频。因此,Flutter 提供了 camera 插件。camera 插件提供了一系列可用的相机,并使用特定的相机展示相机预览、拍照、录视频。

这个章节将会讲解如何使用 camera 插件去展示相机预览、拍照并显示。

步骤

  • 添加所需依赖

  • 获取可用相机列表

  • 创建并初始化 CameraController

  • 使用 CameraPreview 展示相机的帧流

  • 使用 CameraController 拍摄一张图片

  • 使用 Image 组件展示图片

1. 添加所需依赖

为了完成这个章节,你需要向你的应用添加三个依赖:

  • camera - 提供使用设备相机模块的工具

  • path_provider - 寻找存储图片的正确路径

  • path - 创建适配任何平台的路径

  1. dependencies:
  2. flutter:
  3. sdk: flutter
  4. camera:
  5. path_provider:
  6. path:

2. 获取可用相机列表

接着,你可以使用 camera 插件获取可用相机列表。

  1. // Obtain a list of the available cameras on the device.
  2. final cameras = await availableCameras();
  3. // Get a specific camera from the list of available cameras
  4. final firstCamera = cameras.first;

3. 创建并初始化 CameraController

在选择了一个相机后,你需要创建并初始化 CameraController。在这个过程中,与设备相机建立了连接并允许你控制相机并展示相机的预览帧流。

实现这个过程,请依照以下步骤:

  • 创建一个带有 State 类的 StatefulWidget 组件

  • 添加一个变量到 State 类来存放 CameraController

  • 添加另外一个变量到 State 类中来存放 CameraController.initialize 返回的 Future

  • initState 方法中创建并初始化控制器

  • dispose 方法中销毁控制器

  1. // A screen that takes in a list of Cameras and the Directory to store images.
  2. class TakePictureScreen extends StatefulWidget {
  3. final CameraDescription camera;
  4. const TakePictureScreen({
  5. Key key,
  6. @required this.camera,
  7. }) : super(key: key);
  8. @override
  9. TakePictureScreenState createState() => TakePictureScreenState();
  10. }
  11. class TakePictureScreenState extends State<TakePictureScreen> {
  12. // Add two variables to the state class to store the CameraController and
  13. // the Future
  14. CameraController _controller;
  15. Future<void> _initializeControllerFuture;
  16. @override
  17. void initState() {
  18. super.initState();
  19. // In order to display the current output from the Camera, you need to
  20. // create a CameraController.
  21. _controller = CameraController(
  22. // Get a specific camera from the list of available cameras
  23. widget.camera,
  24. // Define the resolution to use
  25. ResolutionPreset.medium,
  26. );
  27. // Next, you need to initialize the controller. This returns a Future
  28. _initializeControllerFuture = _controller.initialize();
  29. }
  30. @override
  31. void dispose() {
  32. // Make sure to dispose of the controller when the Widget is disposed
  33. _controller.dispose();
  34. super.dispose();
  35. }
  36. @override
  37. Widget build(BuildContext context) {
  38. // Fill this out in the next steps
  39. }
  40. }

请注意::If you do not initialize the CameraController, you cannot use the camerato display a preview and take pictures.

如果你没有初始化 CameraController,你就 不能 使用相机预览和拍照。

4. 在 initState 方法中创建并初始化控制器

接着,你能够使用 camera 中的 CameraPreview 组件来展示相机预览帧流。

请记住: 在使用相机前,请确保控制器已经完成初始化。因此,你一定要等待前一个步骤创建 _initializeControllerFuture 执行完毕才去展示 CameraPreview

你可以使用 FutureBuilder 完成这个任务。

  1. // You must wait until the controller is initialized before displaying the
  2. // camera preview. Use a FutureBuilder to display a loading spinner until the
  3. // controller has finished initializing
  4. FutureBuilder<void>(
  5. future: _initializeControllerFuture,
  6. builder: (context, snapshot) {
  7. if (snapshot.connectionState == ConnectionState.done) {
  8. // If the Future is complete, display the preview
  9. return CameraPreview(_controller);
  10. } else {
  11. // Otherwise, display a loading indicator
  12. return Center(child: CircularProgressIndicator());
  13. }
  14. },
  15. )

5. 使用 CameraController 拍照

你可以使用 CameraControllertakePicture 方法拍照。在这个示例中,创建了一个浮动按钮 FloatingActionButton,用户点击这个按钮,就能通过 CameraController 来拍摄图片。

保存一张图片,需要经过一下三个步骤:

  • 确保相机模块已经被初始化完成

  • 创建图片需要被保存的路径

  • 使用控制器拍摄一张图片并保存结果到上述路径

最好把这些操作都放在 try / catch 方法块中来处理可能发生的异常。

  1. FloatingActionButton(
  2. child: Icon(Icons.camera_alt),
  3. // Provide an onPressed callback
  4. onPressed: () async {
  5. // Take the Picture in a try / catch block. If anything goes wrong,
  6. // catch the error.
  7. try {
  8. // Ensure the camera is initialized
  9. await _initializeControllerFuture;
  10. // Construct the path where the image should be saved using the path
  11. // package.
  12. final path = join(
  13. // In this example, store the picture in the temp directory. Find
  14. // the temp directory using the `path_provider` plugin.
  15. (await getTemporaryDirectory()).path,
  16. '${DateTime.now()}.png',
  17. );
  18. // Attempt to take a picture and log where it's been saved
  19. await _controller.takePicture(path);
  20. } catch (e) {
  21. // If an error occurs, log the error to the console.
  22. print(e);
  23. }
  24. },
  25. )

6. 在 dispose 方法中销毁控制器

如果你能成功拍摄图片,你就可以使用 Image 组件展示被保存的图片。在这个示例中,这张图片是以文件的形式存储在设备中。

因此,你需要提供一个 FileImage.file 构造函数。你能够通过传递你在上一步中创建的路径来创建一个 File 类的实例。

  1. Image.file(File('path/to/my/picture.png'))

完整示例

  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:camera/camera.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:path/path.dart' show join;
  6. import 'package:path_provider/path_provider.dart';
  7. Future<void> main() async {
  8. // Obtain a list of the available cameras on the device.
  9. final cameras = await availableCameras();
  10. // Get a specific camera from the list of available cameras
  11. final firstCamera = cameras.first;
  12. runApp(
  13. MaterialApp(
  14. theme: ThemeData.dark(),
  15. home: TakePictureScreen(
  16. // Pass the appropriate camera to the TakePictureScreen Widget
  17. camera: firstCamera,
  18. ),
  19. ),
  20. );
  21. }
  22. // A screen that allows users to take a picture using a given camera
  23. class TakePictureScreen extends StatefulWidget {
  24. final CameraDescription camera;
  25. const TakePictureScreen({
  26. Key key,
  27. @required this.camera,
  28. }) : super(key: key);
  29. @override
  30. TakePictureScreenState createState() => TakePictureScreenState();
  31. }
  32. class TakePictureScreenState extends State<TakePictureScreen> {
  33. CameraController _controller;
  34. Future<void> _initializeControllerFuture;
  35. @override
  36. void initState() {
  37. super.initState();
  38. // In order to display the current output from the Camera, you need to
  39. // create a CameraController.
  40. _controller = CameraController(
  41. // Get a specific camera from the list of available cameras
  42. widget.camera,
  43. // Define the resolution to use
  44. ResolutionPreset.medium,
  45. );
  46. // Next, you need to initialize the controller. This returns a Future
  47. _initializeControllerFuture = _controller.initialize();
  48. }
  49. @override
  50. void dispose() {
  51. // Make sure to dispose of the controller when the Widget is disposed
  52. _controller.dispose();
  53. super.dispose();
  54. }
  55. @override
  56. Widget build(BuildContext context) {
  57. return Scaffold(
  58. appBar: AppBar(title: Text('Take a picture')),
  59. // You must wait until the controller is initialized before displaying the
  60. // camera preview. Use a FutureBuilder to display a loading spinner until
  61. // the controller has finished initializing
  62. body: FutureBuilder<void>(
  63. future: _initializeControllerFuture,
  64. builder: (context, snapshot) {
  65. if (snapshot.connectionState == ConnectionState.done) {
  66. // If the Future is complete, display the preview
  67. return CameraPreview(_controller);
  68. } else {
  69. // Otherwise, display a loading indicator
  70. return Center(child: CircularProgressIndicator());
  71. }
  72. },
  73. ),
  74. floatingActionButton: FloatingActionButton(
  75. child: Icon(Icons.camera_alt),
  76. // Provide an onPressed callback
  77. onPressed: () async {
  78. // Take the Picture in a try / catch block. If anything goes wrong,
  79. // catch the error.
  80. try {
  81. // Ensure the camera is initialized
  82. await _initializeControllerFuture;
  83. // Construct the path where the image should be saved using the path
  84. // package.
  85. final path = join(
  86. // In this example, store the picture in the temp directory. Find
  87. // the temp directory using the `path_provider` plugin.
  88. (await getTemporaryDirectory()).path,
  89. '${DateTime.now()}.png',
  90. );
  91. // Attempt to take a picture and log where it's been saved
  92. await _controller.takePicture(path);
  93. // If the picture was taken, display it on a new screen
  94. Navigator.push(
  95. context,
  96. MaterialPageRoute(
  97. builder: (context) => DisplayPictureScreen(imagePath: path),
  98. ),
  99. );
  100. } catch (e) {
  101. // If an error occurs, log the error to the console.
  102. print(e);
  103. }
  104. },
  105. ),
  106. );
  107. }
  108. }
  109. // A Widget that displays the picture taken by the user
  110. class DisplayPictureScreen extends StatelessWidget {
  111. final String imagePath;
  112. const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);
  113. @override
  114. Widget build(BuildContext context) {
  115. return Scaffold(
  116. appBar: AppBar(title: Text('Display the Picture')),
  117. // The image is stored as a file on the device. Use the `Image.file`
  118. // constructor with the given path to display the image
  119. body: Image.file(File(imagePath)),
  120. );
  121. }
  122. }