Flutter GetX 使用 Camera 实现拍照功能

1,069 阅读2分钟

需求

用前置摄像头采集用户头像

1. 添加所需依赖

运行 flutter pub add 将Camera模块其添加为依赖:

flutter pub add camera

2. 获取可用相机列表

使用相机首先要获取可用相机列表。

// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();

// 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;

3. 创建并初始化 CameraController

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

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

Future<void> _initCamera() async {
  List<CameraDescription> cameras = await availableCameras();
  CameraDescription frontCamera = cameras[0];
  for (var camera in cameras) {
    if (camera.lensDirection == CameraLensDirection.front) {
      frontCamera = camera;
      break;
    }
  }
  controller = CameraController(frontCamera, ResolutionPreset.max, enableAudio: false);
  try {
    // 初始化
    await controller.initialize();
    isCameraInitialized.value = true;
    applyEnabled.value = true;
  } on CameraException catch (e) {
    print(e);
  }
}

@override
void onInit() {
  _initCamera();
  super.onInit();
}

// 需要销毁Controller
@override
void onClose() {
  controller.dispose();
  super.onClose();
}

// 组件
Widget _buildCamera() {
  return Obx(() {
    if (!controller.isCameraInitialized.value) {
      return Container(
        color: Colors.black54,
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      return  CameraPreview(controller.controller);
    }
  });
}

注意

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

4. 使用 CameraController 拍照

接着就可以调用controller.takePicture()来拍照,它获得一个XFile对象,直接就可以通过path展示出来。

Future<void> takePicture() async {
  if (!isCameraInitialized.value) {
    return;
  }
  applyEnabled.value = false;
  try {
    final XFile file = await controller.takePicture();
    imagePath.value = file.path;
    });
  } on CameraException catch (e) {
    applyEnabled.value = true;
  }
}

// 展示图片
Widget _buildShowPicture() {
  return Obx(
   () => controller.imagePath.value.isEmpty
       ? SizedBox()
       : Image.file(
           File(controller.imagePath.value),
           width: 100,
           fit: BoxFit.fitWidth,
         ),
 );
}

// 拍照
FloatingActionButton(
  // Provide an onPressed callback.
  onPressed: () async {
    controller.apply();
  },
  child: const Icon(Icons.camera_alt),
)

问题

以上已经可以拍照了,但是会发现预览镜头是横向的,拍摄出来的图片是正常竖屏的,所以需要将预览镜头进行旋转。

Widget _buildCamera() {
  return Obx(() {
    if (!controller.isCameraInitialized.value) {
      return Container(
        color: Colors.black54,
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      return Transform.rotate(
        angle: -pi / 2, // 将预览画面旋转 90 度
        child: Center(
            child: AspectRatio(
              aspectRatio: controller.controller.value.aspectRatio,
              child: CameraPreview(controller.controller),
            ),
         ),
      );
    }
  });
}

同样,在预览中有可能会出现拉伸现象,所以需要进行缩放。

Widget _buildCamera() {
  return Obx(() {
    if (!controller.isCameraInitialized.value) {
      return Container(
        color: Colors.black54,
        child: Center(
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      return Transform.rotate(
        angle: -pi / 2, // 将预览画面旋转 90 度
        child: Transform.scale(
          scale: Get.pixelRatio,
          child: Center(
            child: AspectRatio(
              aspectRatio: controller.controller.value.aspectRatio,
              child: CameraPreview(controller.controller),
            ),
          ),
        ),
      );
    }
  });
}

这里有个疑问,采用final scale = Get.window.physicalSize / controller.value.previewSize.width 计算出来的 scale = 1, 但展示其实只有设备的一半左右,实际像素和展示像素不一致,故而采用Get.pixelRatio计算缩放。