一、camera 插件

camera 是 Flutter 提供的相机相关的插件,内部封装了相机相关的 Native API,通过 camera 插件能够获取当前设备相机列表,并且选择一个可用相机展示相机预览、拍照和录制视频等

要使用 camera 插件,添加依赖,一般我们还需要 path_provider 和 path 两个包,用于存储和获取照片

dependencies:
  flutter:
    sdk: flutter
  camera:
  path_provider:
  path:

二、availableCameras 获取设备可用的相机列表

availableCamerascamera.dart 提供的用于获取设备上相机列表的方法

Native 会将设备相机列表通过通信传递给 Flutter,然后再返回一个 List

    return cameras.map((Map<dynamic, dynamic> camera) {
      return CameraDescription(
        name: camera['name'],
        lensDirection: _parseCameraLensDirection(camera['lensFacing']),
        sensorOrientation: camera['sensorOrientation'],
      );
    }).toList();

方法都是异步的,因此获取设备的一个主摄像头(一般是后置)可以通过如下方式:

// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();

// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first; 

当然,你可以通过判断相机列表来判断是否有可用相机,然后做一个提示

  final cameras = await availableCameras();
  Widget _body;
  if (cameras.length > 0) {
    _body = HomeContent(camera: cameras[0]);
  } else {
    _body = Center(child: Text('No Cameras'));
  }

三、创建 CameraController 及初始化

拿到设备相机之后,就可以创建 CameraController:

 _cameraController = CameraController(widget.camera, ResolutionPreset.medium);

CameraController 需要传入两个顺序参数,分别是描述和预设分辨率

  CameraController(
    this.description,
    this.resolutionPreset, {
    this.enableAudio = true,
  })

description 需要是 CameraDescription 类型

ResolutionPreset 有三个预设值分别是 low/medium/high

拿到 Controller 之后,就可以初始化设备上的相机,提供了 initialize() 方法

   _initializeControllerFuture = _cameraController.initialize();

当然在 Widget 销毁的时候,需要将 CameraController 移除:

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

四、使用 CameraPreview 预览相机

拍照一般我们都会有一个预览窗口,预览窗口能够实时看到相机看到的内容

CameraPreview 使用很简单,只需要将 CameraController 的实例作为参数区实例化出 CameraPreview 即可。

CameraPreview 的宽度高度则是依赖父容器,一般用一个 Container:

return Container(
  child: CameraPreview(_cameraController),
  width: 400,
  height: 300,
);

当然,我们可以发现,CameraContoller.initialize() 方法也是异步的,如果需要等待异步完成在渲染页面,就需要使用 FutureBuilder 去渲染 Widget。

下面代码中,当 _initializeControllerFuture 完成的时候,才将 CameraPreview 渲染:

FutureBuilder<void>(
  future: _initializeControllerFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      // If the Future is complete, display the preview.
      return Container(
        child: CameraPreview(_cameraController),
        width: 400,
        height: 300,
      );
    } else {
      // Otherwise, display a loading indicator.
      return Center(child: CircularProgressIndicator());
    }
  },

五、使用 takePicture 拍照

CameraController 提供了 takePicture 进行拍照, Future<void> takePicture(String path) async {}

拍照的时候需要将 path 也就是保存的路径传入

生成路径一般我们需要选择一个存储的文件夹,通过 path_provider 提供,通过 path 将路径组装起来

下面代码中,通过时间戳生成一个 path,然后再拼接整个路径:

try {
  await _initializeControllerFuture;
  final dateTime = DateTime.now();
  final path = join((await getTemporaryDirectory()).path,
      '${dateTime.millisecondsSinceEpoch}.png');
  await _cameraController.takePicture(path);

  Navigator.of(context).push(MaterialPageRoute(
    builder: (context) => DisplayPictureScreen(imagePath: path),
  ));
  Scaffold.of(context).showSnackBar(SnackBar(content: Text(path)));
} catch (err, stack) {
  print(err);
}

注意:上面代码中,await _initializeControllerFuture; 确保已经初始化过了 CameraController

通过 SnackBar 显示路径:

25864-0arrkxk8aj26.png

六、效果

23455-wh6pottf47l.png

七、完整代码

https://github.com/postbird/FlutterHelloWorldDemo/blob/dev1/demo1/lib/bak/main.56-Camera.dart