flutter开发实战-自定义相机camera功能。
Flutter 本质上只是一个 UI 框架,运行在宿主平台之上,Flutter 本身是无法提供一些系统能力,比如使用蓝牙、相机、GPS等,因此要在 Flutter 中调用这些能力就必须和原生平台进行通信。
实现相机功能,我们使用的是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
处理详情权限获取,以下是权限错误的类型
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),
);
}
}
通过重写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);
}
}
这个需要使用到具体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!,
),
),
),
),
),
),),);
}
使用拍照功能,需要用到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。
暂停及恢复预览
if (!cameraController.value.isPreviewPaused) {
await cameraController.pausePreview();
}
if (cameraController.value.isPreviewPaused) {
await cameraController.resumePreview();
}
裁剪图片这里使用的是插件: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功能,拍照及图片裁剪功能.
学习记录,每天不停进步。