Flutter相机功能插件penguin_camera的使用

Flutter相机功能插件penguin_camera的使用

penguin_camera

一个跨平台框架,用于访问设备上可用的各种摄像头及其特性,允许你在应用中捕获图片和视频。

除了提供跨平台的接口外,该框架还提供了对平台特定功能的简便访问。

支持平台

  • Android 16+
  • iOS 10.0+

权限

Android 和 iOS 需要权限才能访问摄像头和音频设备,但此包不提供请求这些权限的 API。建议使用 permission_handler 包来请求权限。请参见示例中的示例。

使用案例

获取摄像头设备信息

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
for (CameraDevice device in devices) {
  print('CameraDevice: ${device.name}, ${device.position}');
}

显示预览

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
final CameraDevice device = devices.firstWhere(
  (CameraDevice device) => device.position == CameraPosition.back,
);

final PreviewOutput previewOutput = PreviewOutput();
final CameraController controller = CameraController(
  device: device,
  outputs: <CameraOutput>[previewOutput],
);

// 这必须在调用其他方法之前完成。
await controller.initialize();

final Widget previewWidget = await previewOutput.previewWidget();

controller.start();

捕获图片

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
final CameraDevice device = devices.firstWhere(
  (CameraDevice device) => device.position == CameraPosition.back,
);

final ImageCaptureOutput imageOutput = ImageCaptureOutput();
// Android 需要 PreviewOutput,所以尽管它未被使用也添加了。
final CameraController controller = CameraController(
  device: device,
  outputs: <CameraOutput>[PreviewOutput(), imageOutput],
);
// 这必须在调用其他方法之前完成。
await controller.initialize();
controller.start();

imageOutput.takePicture((Uint8List data) {
  print(data.length);
});

controller.stop();
controller.dispose();

录制视频

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
final CameraDevice device = devices.firstWhere(
  (CameraDevice device) => device.position == CameraPosition.back,
);

final VideoCaptureOutput videoOutput = VideoCaptureOutput();
// Android 需要 PreviewOutput,所以尽管它未被使用也添加了。
final CameraController controller = CameraController(
  device: device,
  outputs: <CameraOutput>[PreviewOutput(), videoOutput],
);
// 这必须在调用其他方法之前完成。
await controller.initialize();
controller.start();

videoOutput.startRecording(fileOutput: 'myFile');
videoOutput.stopRecording();

controller.stop();
controller.dispose();

设置曝光

final CameraController controller = ....
final List<ExposureMode> modes = await controller.supportedExposureModes();
if (modes.contains(ExposureMode.continuous)) {
  controller.setExposureMode(ExposureMode.continuous);
}

设置焦点

final CameraController controller = ....
final List<FocusMode> modes = await controller.supportedFocusModes();
if (modes.contains(FocusMode.continuousImageAutoFocus)) {
  controller.setFocusMode(FocusMode.continuousImageAutoFocus);
}

设置缩放

final CameraController controller = ....
if (await controller.zoomSupported()) {
  final double minZoom = await controller.minZoom();
  final double maxZoom = await controller.maxZoom();
  controller.setZoom((maxZoom + minZoom) / 2);
}

获取输出大小

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
final CameraDevice device = devices.firstWhere(
  (CameraDevice device) => device.position == CameraPosition.back,
);

final PreviewOutput previewOutput = PreviewOutput();
final CameraController controller = CameraController(
  device: device,
  outputs: <CameraOutput>[previewOutput],
);

// 这必须完成以保证 CameraOutput.outputSize() 返回非空。
await controller.setControllerPreset(CameraControllerPreset.high);
final Size? outputSize = await previewOutput.outputSize();

设置闪光灯

final List<CameraDevice> devices = await CameraController.getAllCameraDevices();
final CameraDevice device = devices.firstWhere(
  (CameraDevice device) => device.position == CameraPosition.back,
);

final ImageCaptureOutput imageOutput = ImageCaptureOutput();
// Android 需要 PreviewOutput,所以尽管它未被使用也添加了。
final CameraController controller = CameraController(
  device: device,
  outputs: <CameraOutput>[PreviewOutput(), imageOutput],
);
// 这必须在调用其他方法之前完成。
await controller.initialize();

final List<FlashMode> modes = await imageOutput.supportedFlashModes();
if (modes.contains(FlashMode.auto)) {
  imageOutput.setFlashMode(FlashMode.auto);
}

使用 Android 特定功能

import 'package:penguin_camera/penguin_camera.dart';
import 'package:penguin_camera/android.dart' as android;

final CameraController controller = ....
if (defaultTargetPlatform == TargetPlatform.android) {
  final android.CameraController androidController =
      controller as android.CameraController;

  final android.CameraParameters parameters =
      androidController.cameraParameters;
  parameters.setColorEffect(android.CameraParameters.effectNegative);
  androidController.camera.setParameters(parameters);
}

使用 iOS 特定功能

import 'package:penguin_camera/penguin_camera.dart';
import 'package:penguin_camera/ios.dart' as ios;

final CameraController controller = ....
if (defaultTargetPlatform == TargetPlatform.iOS) {
  final ios.CameraController iosController =
      controller as ios.CameraController;

  iosController.session.setSessionPreset(ios.CaptureSessionPreset.photo);
}

完整示例代码

// ignore_for_file: public_member_api_docs

import 'dart:io';
import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:penguin_camera/penguin_camera.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MaterialApp(home: _MyApp()));
}

enum CameraMode {
  prePicture,
  preVideo,
  picture,
  video,
  none,
  videoRecording,
}

class _MyApp extends StatefulWidget {
  const _MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<_MyApp> {
  CameraMode currentMode = CameraMode.none;

  late CameraController _cameraController;
  PreviewOutput _previewOutput = PreviewOutput();
  late ImageCaptureOutput _imageCaptureOutput;
  late VideoCaptureOutput _videoCaptureOutput;
  Size? _previewOutputSize;

  CameraPosition _cameraPosition = CameraPosition.front;

  final double _deviceRotation = 0;

  [@override](/user/override)
  void initState() {
    super.initState();
    SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.manual,
      overlays: [SystemUiOverlay.bottom],
    );
    SystemChrome.setPreferredOrientations(<DeviceOrientation>[
      DeviceOrientation.portraitUp,
    ]);
    _setupForPicture();
  }

  Future<void> _getPicturePermission() async {
    while (!(await Permission.camera.request().isGranted)) {}
    while (!(await Permission.storage.request().isGranted)) {}

    if (defaultTargetPlatform == TargetPlatform.iOS) {
      while (!(await Permission.photosAddOnly.request().isGranted)) {}
    }
  }

  Future<void> _getAudioPermission() async {
    while (!(await Permission.microphone.request().isGranted)) {}
  }

  Future<void> _setupForPicture() async {
    await _getPicturePermission();

    _imageCaptureOutput = ImageCaptureOutput();
    _cameraController = await _setupCameraController(_imageCaptureOutput);

    final List<FocusMode> supportedFocusMode =
        await _cameraController.supportedFocusModes();
    if (supportedFocusMode.contains(FocusMode.continuousImageAutoFocus)) {
      _cameraController.setFocusMode(FocusMode.continuousImageAutoFocus);
    }

    _cameraController.start();
    setState(() {
      currentMode = CameraMode.picture;
    });
  }

  Future<void> _setupForVideo() async {
    await _getPicturePermission();
    await _getAudioPermission();

    _videoCaptureOutput = VideoCaptureOutput(includeAudio: true);
    _cameraController = await _setupCameraController(_videoCaptureOutput);
    final List<FocusMode> supportedFocusMode =
        await _cameraController.supportedFocusModes();
    if (supportedFocusMode.contains(FocusMode.continuousVideoAutoFocus)) {
      _cameraController.setFocusMode(FocusMode.continuousVideoAutoFocus);
    }
    _cameraController.start();
    setState(() {
      currentMode = CameraMode.video;
    });
  }

  Future<CameraController> _setupCameraController(CameraOutput output) async {
    final List<CameraDevice> allCameraDevices =
        await CameraController.getAllCameraDevices();

    final CameraDevice device = allCameraDevices.firstWhere(
      (CameraDevice device) => device.position == _cameraPosition,
    );

    _previewOutput = PreviewOutput();
    final CameraController cameraController = CameraController(
      device: device,
      outputs: <CameraOutput>[_previewOutput, output],
    );

    await cameraController.initialize();

    await cameraController.setControllerPreset(CameraControllerPreset.high);
    _previewOutput.setRotation(OutputRotation.rotation0);
    output.setRotation(OutputRotation.rotation0);
    _previewOutputSize = await _previewOutput.outputSize();
    return cameraController;
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    if (currentMode != CameraMode.none) _cameraController.dispose();
  }

  Future<void> _toggleCameraMode() {
    switch (currentMode) {
      case CameraMode.prePicture:
      case CameraMode.preVideo:
      case CameraMode.none:
        return Future<void>.value();
      case CameraMode.picture:
        setState(() => currentMode = CameraMode.preVideo);
        _cameraController.dispose();
        return _setupForVideo();
      case CameraMode.video:
        setState(() => currentMode = CameraMode.prePicture);
        _cameraController.dispose();
        return _setupForPicture();
      case CameraMode.videoRecording:
        const SnackBar snackBar = SnackBar(
          content: Text('Please end video recording first.'),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
        return Future<void>.value();
    }
  }

  Future<void> _toggleCameraPosition(BuildContext context) {
    void switchCameraPosition() {
      if (_cameraPosition == CameraPosition.front) {
        _cameraPosition = CameraPosition.back;
      } else {
        _cameraPosition = CameraPosition.front;
      }
    }

    switch (currentMode) {
      case CameraMode.prePicture:
      case CameraMode.preVideo:
      case CameraMode.none:
        return Future<void>.value();
      case CameraMode.picture:
        setState(() => currentMode = CameraMode.prePicture);
        _cameraController.dispose();
        switchCameraPosition();
        return _setupForPicture();
      case CameraMode.video:
        setState(() => currentMode = CameraMode.preVideo);
        _cameraController.dispose();
        switchCameraPosition();
        return _setupForVideo();
      case CameraMode.videoRecording:
        const SnackBar snackBar = SnackBar(
          content: Text('Please end video recording first.'),
        );
        ScaffoldMessenger.of(context).showSnackBar(snackBar);
        return Future<void>.value();
    }
  }

  Future<Directory> _getStorageDir() async {
    final Directory? directory;
    if (defaultTargetPlatform == TargetPlatform.android) {
      final List<Directory>? directories = await getExternalStorageDirectories(
        type: StorageDirectory.dcim,
      );
      directory = directories?.first;
    } else {
      directory = await getApplicationDocumentsDirectory();
    }
    if (directory == null) {
      throw StateError('Could not get storage directory.');
    }
    return directory;
  }

  void _takeImage(BuildContext context) {
    _imageCaptureOutput.takePicture((Uint8List bytes) async {
      debugPrint('Image taken with jpeg data length: ${bytes.length}');
      final Directory dir = await _getStorageDir();
      final File imageFile = File('${dir.path}/my_image${bytes.hashCode}.jpg');
      imageFile.writeAsBytes(bytes);

      final String message = 'Picture stored at: $imageFile';
      debugPrint(message);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(message)),
      );
    });
  }

  Future<void> _startRecording() async {
    final Directory dir = await _getStorageDir();
    _videoCaptureOutput.startRecording(
      fileOutput: '${dir.path}/my_video${Random().nextInt(10000)}.mp4',
    );

    setState(() {
      currentMode = CameraMode.videoRecording;
    });
  }

  void _stopRecording() {
    _videoCaptureOutput.stopRecording();
    setState(() {
      currentMode = CameraMode.video;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: <Widget>[
            Expanded(
              child: Container(
                decoration: const BoxDecoration(color: Colors.black),
                child: Center(
                  child: Stack(
                    alignment: Alignment.center,
                    children: <Widget>[
                      CameraPreview(
                        previewOutput: _previewOutput,
                        cameraMode: currentMode,
                        size: _previewOutputSize,
                      ),
                      if (currentMode == CameraMode.picture ||
                          currentMode == CameraMode.video ||
                          currentMode == CameraMode.videoRecording)
                        Positioned(
                          bottom: 0,
                          child: ZoomWidget(_cameraController),
                        )
                    ],
                  ),
                ),
              ),
            ),
            Container(
              decoration: const BoxDecoration(color: Colors.black),
              padding: const EdgeInsets.only(
                bottom: 30,
                left: 10,
                right: 10,
                top: 15,
              ),
              child: Stack(
                children: <Widget>[
                  Container(
                    margin: const EdgeInsets.only(top: 10),
                    alignment: Alignment.centerLeft,
                    child: Transform.rotate(
                      angle: _deviceRotation,
                      child: IconButton(
                        icon: const Icon(
                          Icons.switch_camera,
                          color: Colors.white,
                          size: 32,
                        ),
                        onPressed: () => _toggleCameraPosition(context),
                      ),
                    ),
                  ),
                  Container(
                    alignment: Alignment.center,
                    child: CameraButton(
                      cameraMode: currentMode,
                      onTakePicture: () => _takeImage(context),
                      onStartRecording: _startRecording,
                      onStopRecording: _stopRecording,
                    ),
                  )
                ],
              ),
            ),
            Container(
              alignment: Alignment.center,
              decoration: const BoxDecoration(color: Colors.black),
              child: ToggleButtons(
                color: Colors.blue,
                fillColor: Colors.grey,
                selectedColor: Colors.blue,
                onPressed: (int index) {
                  if (index == 0 && currentMode != CameraMode.picture) {
                    _toggleCameraMode();
                  } else if (index == 1 && currentMode != CameraMode.video) {
                    _toggleCameraMode();
                  }
                },
                isSelected: <bool>[
                  currentMode == CameraMode.picture ||
                      currentMode == CameraMode.prePicture,
                  currentMode == CameraMode.video ||
                      currentMode == CameraMode.preVideo ||
                      currentMode == CameraMode.videoRecording,
                ],
                children: const <Widget>[
                  Icon(Icons.camera_alt),
                  Icon(Icons.videocam),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class ZoomWidget extends StatefulWidget {
  const ZoomWidget(this.controller, {Key? key}) : super(key: key);

  final CameraController controller;

  [@override](/user/override)
  State<StatefulWidget> createState() {
    return ZoomWidgetState();
  }
}

class ZoomWidgetState extends State<ZoomWidget> {
  static const _maxAllowedZoom = 5;

  late int _minZoom;
  late int _maxZoom;
  late int _currentZoom;
  bool _supportsZoom = false;
  bool _supportsSmoothZoom = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    getZoomInfo();
  }

  Future<void> getZoomInfo() async {
    final CameraController controller = widget.controller;

    final bool supportsZoom = await controller.zoomSupported();
    if (!supportsZoom) return;

    final bool supportsSmoothZoom = await controller.smoothZoomSupported();
    final int minZoom = (await controller.minZoom()).ceil();
    final int maxZoom = min(
      (await controller.maxZoom()).floor(),
      _maxAllowedZoom,
    );

    setState(() {
      _supportsZoom = supportsZoom;
      _supportsSmoothZoom = supportsSmoothZoom;
      _currentZoom = 0;
      _minZoom = minZoom;
      _maxZoom = maxZoom;
    });
  }

  Future<void> zoom(int value) {
    if (_supportsSmoothZoom) {
      return widget.controller.smoothZoomTo((value + _minZoom).toDouble());
    }
    return widget.controller.setZoom((value + _minZoom).toDouble());
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    if (!_supportsZoom) return Container();

    final int length = _maxZoom - _minZoom + 1;
    return ToggleButtons(
      color: Colors.blue,
      fillColor: Colors.grey,
      selectedColor: Colors.blue,
      onPressed: (int index) {
        setState(() => _currentZoom = index);
        zoom(index);
      },
      isSelected: List<int>.generate(length, (index) => index)
          .map<bool>((int value) => value == _currentZoom)
          .toList(),
      children: List<int>.generate(length, (index) => index)
          .map<Widget>((int value) => Text('${value + 1}x'))
          .toList(),
    );
  }
}

class CameraPreview extends StatelessWidget {
  const CameraPreview({
    Key? key,
    required this.previewOutput,
    required this.cameraMode,
    this.size,
  }) : super(key: key);

  final PreviewOutput previewOutput;

  final CameraMode cameraMode;

  final Size? size;

  [@override](/user/override)
  Widget build(BuildContext context) {
    switch (cameraMode) {
      case CameraMode.picture:
      case CameraMode.video:
      case CameraMode.videoRecording:
        return FutureBuilder<Widget>(
          future: previewOutput.previewWidget(),
          builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
            if (snapshot.hasData) {
              if (size != null) {
                return AspectRatio(
                  aspectRatio: size!.height / size!.width,
                  child: snapshot.data!,
                );
              }
              return snapshot.data!;
            }

            return Container();
          },
        );
      case CameraMode.prePicture:
      case CameraMode.preVideo:
      case CameraMode.none:
        return Container();
    }
  }
}

class CameraButton extends StatelessWidget {
  const CameraButton({
    Key? key,
    required this.cameraMode,
    required this.onTakePicture,
    required this.onStartRecording,
    required this.onStopRecording,
  }) : super(key: key);

  final CameraMode cameraMode;
  final VoidCallback onTakePicture;
  final Future<void> Function() onStartRecording;
  final VoidCallback onStopRecording;

  [@override](/user/override)
  Widget build(BuildContext context) {
    switch (cameraMode) {
      case CameraMode.picture:
      case CameraMode.prePicture:
        return CircledIconButton(icon: Icons.camera_alt, onTap: onTakePicture);
      case CameraMode.video:
      case CameraMode.preVideo:
        return CircledIconButton(icon: Icons.videocam, onTap: onStartRecording);
      case CameraMode.videoRecording:
        return CircledIconButton(
          icon: Icons.videocam,
          color: Colors.red,
          onTap: onStopRecording,
        );
      case CameraMode.none:
        return Container();
    }
  }
}

class CircledIconButton extends StatelessWidget {
  const CircledIconButton({
    Key? key,
    required this.icon,
    this.onTap,
    this.color = Colors.white,
  }) : super(key: key);

  final IconData icon;

  final VoidCallback? onTap;

  final Color color;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return InkResponse(
      onTap: () {
        if (onTap != null) onTap!();
      },
      child: Container(
        width: 65,
        height: 65,
        decoration: BoxDecoration(
          color: color,
          shape: BoxShape.circle,
          border: Border.all(color: Colors.grey, width: 2),
        ),
        child: Icon(icon, color: Colors.grey, size: 60),
      ),
    );
  }
}

更多关于Flutter相机功能插件penguin_camera的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter相机功能插件penguin_camera的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


penguin_camera 是一个 Flutter 插件,用于在 Flutter 应用中集成相机功能。它提供了一个简单易用的 API,允许开发者快速实现拍照、录像、切换摄像头等功能。以下是如何使用 penguin_camera 插件的基本步骤。

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 penguin_camera 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  penguin_camera: ^0.0.1  # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 基本使用

2.1 导入包

在你的 Dart 文件中导入 penguin_camera 包:

import 'package:penguin_camera/penguin_camera.dart';

2.2 初始化相机

你可以使用 PenguinCamera 小部件来初始化相机并显示相机预览:

class CameraScreen extends StatefulWidget {
  [@override](/user/override)
  _CameraScreenState createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  PenguinCameraController? _controller;

  [@override](/user/override)
  void initState() {
    super.initState();
    _controller = PenguinCameraController();
  }

  [@override](/user/override)
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Camera Example'),
      ),
      body: PenguinCamera(
        controller: _controller,
      ),
    );
  }
}

2.3 拍照

你可以使用 PenguinCameraControllertakePicture 方法来拍照:

void _takePicture() async {
  if (_controller != null) {
    final image = await _controller!.takePicture();
    // 处理拍摄的照片,例如保存到本地或显示在界面上
    print('Picture taken: ${image.path}');
  }
}

2.4 切换摄像头

你可以使用 PenguinCameraControllerswitchCamera 方法来切换前后摄像头:

void _switchCamera() {
  if (_controller != null) {
    _controller!.switchCamera();
  }
}

2.5 录制视频

penguin_camera 也支持录制视频。你可以使用 startRecordingstopRecording 方法来控制视频录制:

void _startRecording() async {
  if (_controller != null) {
    await _controller!.startRecording();
    print('Recording started');
  }
}

void _stopRecording() async {
  if (_controller != null) {
    final video = await _controller!.stopRecording();
    // 处理录制的视频,例如保存到本地或显示在界面上
    print('Recording stopped: ${video.path}');
  }
}

3. 其他功能

penguin_camera 还提供了其他一些功能,例如:

  • 闪光灯控制:你可以使用 setFlashMode 方法来控制闪光灯的模式(自动、开启、关闭)。
  • 相机分辨率:你可以通过 setResolution 方法来设置相机的分辨率。

4. 注意事项

  • 在使用相机功能时,确保在 AndroidManifest.xmlInfo.plist 中添加必要的权限。
  • 由于相机功能依赖于设备硬件,建议在真实设备上进行测试。

5. 示例代码

以下是一个完整的示例代码,展示了如何使用 penguin_camera 插件实现拍照和切换摄像头功能:

import 'package:flutter/material.dart';
import 'package:penguin_camera/penguin_camera.dart';

class CameraScreen extends StatefulWidget {
  [@override](/user/override)
  _CameraScreenState createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  PenguinCameraController? _controller;

  [@override](/user/override)
  void initState() {
    super.initState();
    _controller = PenguinCameraController();
  }

  [@override](/user/override)
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  void _takePicture() async {
    if (_controller != null) {
      final image = await _controller!.takePicture();
      print('Picture taken: ${image.path}');
    }
  }

  void _switchCamera() {
    if (_controller != null) {
      _controller!.switchCamera();
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Camera Example'),
      ),
      body: Column(
        children: [
          Expanded(
            child: PenguinCamera(
              controller: _controller,
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              IconButton(
                icon: Icon(Icons.camera),
                onPressed: _takePicture,
              ),
              IconButton(
                icon: Icon(Icons.switch_camera),
                onPressed: _switchCamera,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

void main() => runApp(MaterialApp(
  home: CameraScreen(),
));
回到顶部