flutter开发实战-自定义相机camera功能

flutter开发实战-自定义相机camera功能。

Flutter 本质上只是一个 UI 框架,运行在宿主平台之上,Flutter 本身是无法提供一些系统能力,比如使用蓝牙、相机、GPS等,因此要在 Flutter 中调用这些能力就必须和原生平台进行通信。
实现相机功能,我们使用的是camera插件。

一、引入camera插件

在pubspec.yaml引入插件

  # Camera相机拍照等
  camera: ^0.10.5+2
  image: ^4.0.17

二、实现相机拍照功能

在iOS的info.plist文件增加相机、麦克风权限

<key>NSCameraUsageDescription</key>
<string>your usage description here</string>
<key>NSMicrophoneUsageDescription</key>
<string>your usage description here</string>

在Android的android/app/build.gradle调整

minSdkVersion 21

处理详情权限获取,以下是权限错误的类型

  • CameraAccessDenied:当用户拒绝相机访问权限时抛出。
  • CameraAccessDeniedWithoutPrompt:仅限iOS。当用户先前拒绝该权限时抛出。iOS不允许再次提示警报对话框。用户必须进入“设置”>“隐私”>“相机”才能访问相机。
  • CameraAccessRestricted:仅限iOS。当摄像头访问受到限制且用户无法授予权限(家长控制)时抛出。
  • AudioAccessDenied:当用户拒绝音频访问权限时抛出。
  • AudioAccessDeniedWithoutPrompt:目前仅限iOS。当用户先前拒绝该权限时抛出。iOS不允许再次提示警报对话框。用户必须转到“设置”>“隐私”>“麦克风”才能启用音频访问。
  • AudioAccessRestricted:目前仅限iOS。当音频访问受到限制并且用户无法授予权限(家长控制)时抛出。

2.1 实现相机

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

late List<CameraDescription> _cameras;

Future<void> main() async {
    
    
  WidgetsFlutterBinding.ensureInitialized();

  _cameras = await availableCameras();
  runApp(const CameraApp());
}

/// CameraApp is the Main Application.
class CameraApp extends StatefulWidget {
    
    
  /// Default Constructor
  const CameraApp({
    
    super.key});

  
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
    
    
  late CameraController controller;

  
  void initState() {
    
    
    super.initState();
    controller = CameraController(_cameras[0], ResolutionPreset.max);
    controller.initialize().then((_) {
    
    
      if (!mounted) {
    
    
        return;
      }
      setState(() {
    
    });
    }).catchError((Object e) {
    
    
      if (e is CameraException) {
    
    
        switch (e.code) {
    
    
          case 'CameraAccessDenied':
            // Handle access errors here.
            break;
          default:
            // Handle other errors here.
            break;
        }
      }
    });
  }

  
  void dispose() {
    
    
    controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    
    
    if (!controller.value.isInitialized) {
    
    
      return Container();
    }
    return MaterialApp(
      home: CameraPreview(controller),
    );
  }
}

2.2 生命周期更改时处理相机

通过重写didChangeAppLifecycleState方法来处理生命周期更改,如下所示:

使用WidgetsBindingObserver


  void didChangeAppLifecycleState(AppLifecycleState state) {
    
    
    final CameraController? cameraController = controller;

    // App state changed before we got the chance to initialize.
    if (cameraController == null || !cameraController.value.isInitialized) {
    
    
      return;
    }

    if (state == AppLifecycleState.inactive) {
    
    
      cameraController.dispose();
    } else if (state == AppLifecycleState.resumed) {
    
    
      _initializeCameraController(cameraController.description);
    }
  }

2.3 CameraPreview预览时图像变形问题更改

这个需要使用到具体Container的宽高和aspectRatio做处理

    if (controller != null && controller!.value.isInitialized) {
    
    
      // 设备像素比
      double deviceRatio = 1.0;
      if (widget.width! > widget.height!) {
    
    
        deviceRatio = widget.width! / widget.height!;
      } else {
    
    
        deviceRatio = widget.height! / widget.width!;
      }
      // 相机纵横比
      final double aspectRatio = controller!.value.aspectRatio;

      double scale = aspectRatio / deviceRatio;
      return Container(
        key: _cameraContainerGlobalKey,
        width: widget.width,
        height: widget.height,
        clipBehavior: Clip.hardEdge,
        decoration: BoxDecoration(
          color: Colors.transparent,
          borderRadius: BorderRadius.circular(20.r),
        ),
        child: Stack(
          alignment: Alignment.center,
          clipBehavior: Clip.hardEdge,
          children: [
            Container(
              width: widget.width,
              height: widget.height,
              clipBehavior: Clip.hardEdge,
              decoration: BoxDecoration(
                color: Colors.transparent,
              ),
              child: RepaintBoundary(
                key: _cameraViewGlobalKey,
                child: Transform.scale(
                  scale: scale * aspectRatio,
                  child: AspectRatio(
                    aspectRatio: aspectRatio,
                    child: Center(
                      child: CameraPreview(
                        controller!,
                      ),
                    ),
                  ),
                ),
              ),
            ),),);
}

2.4 实现拍照功能

使用拍照功能,需要用到CameraController

Future<XFile?> takePicture() async {
    
    
    final CameraController? cameraController = controller;
    if (cameraController == null || !cameraController.value.isInitialized) {
    
    
      print('Error: select a camera first.');
      return null;
    }

    if (cameraController.value.isTakingPicture) {
    
    
      // A capture is already pending, do nothing.
      return null;
    }

    try {
    
    
      final XFile file = await cameraController.takePicture();
      print("takePicture file:${
      
      file.toString()}");
      return file;
    } on CameraException catch (e) {
    
    
      print("takePicture exception:${
      
      e.toString()}");
      return null;
    }
  }

获取到File,可以得到图片的path。

2.5 暂停及恢复预览

暂停及恢复预览

if (!cameraController.value.isPreviewPaused) {
    
    
      await cameraController.pausePreview();
    }
if (cameraController.value.isPreviewPaused) {
    
    
      await cameraController.resumePreview();
    }

2.6 获得图片后裁剪

裁剪图片这里使用的是插件:image

引入插件

image: ^4.0.17

实现裁剪

if (file != null) {
    
    
          // 保存到相册
          // await SaveToAlbumUtil.saveLocalImage(file.path);
          RenderBox renderBox = _cameraContainerGlobalKey.currentContext!
              .findRenderObject() as RenderBox;
          // offset.dx , offset.dy 就是控件的左上角坐标
          Offset offset = renderBox.localToGlobal(Offset.zero);
          //获取size
          Size size = renderBox.size;

          // 创建文件path
          String imageDir = await PathUtil.createDirectory("local_images");
          String imagePath = '$imageDir/${
      
      TimeUtil.currentTimeMillis()}.png';

          // 获取当前设备的像素比
          double dpr = ui.window.devicePixelRatio;
          print("devicePixelRatio:${
      
      dpr}");
          print(
              "offset:(${
      
      offset.dx},${
      
      offset.dy})--size:(${
      
      size.width},${
      
      size.height})");
          File? targetFile = await ImageUtil.cropImage(file.path, imagePath,
              x: (dpr * offset.dx).floor(),
              y: (dpr * offset.dy).floor(),
              width: (dpr * size.width).ceil(),
              height: (dpr * size.height).ceil());
          if (targetFile != null) {
    
    
            await SaveToAlbumUtil.saveLocalImage(targetFile.path);
          }
}

裁剪结果如图所示

在这里插入图片描述
在这里插入图片描述

三、小结

flutter开发实战-自定义相机camera功能,拍照及图片裁剪功能.

学习记录,每天不停进步。

猜你喜欢

转载自blog.csdn.net/gloryFlow/article/details/131926795