当前位置: 首页 > news >正文

如何做镜像网站百度账号官网

如何做镜像网站,百度账号官网,域名自动跳转其他网站,建网站需要注册公司吗flutter开发实战-Camera自定义相机拍照功能实现 一、前言 在项目中使用image_picker插件时候,在android设备上使用无法默认设置前置摄像头(暂时不清楚什么原因),由于项目默认需要使用前置摄像头,所以最终采用自定义…

flutter开发实战-Camera自定义相机拍照功能实现
在这里插入图片描述

一、前言

在项目中使用image_picker插件时候,在android设备上使用无法默认设置前置摄像头(暂时不清楚什么原因),由于项目默认需要使用前置摄像头,所以最终采用自定义相机实现拍照功能。

二、Camera使用前设置

在工程的iOS的info.plist文件中添加相机、麦克风权限描述

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

在工程的Android的gradle设置minSdkVersion

找到android/app/build.gradle文件

minSdkVersion 21

二、使用插件Camera插件

camera : 适用于iOS、Android和Web的Flutter插件,允许访问设备摄像头。

我们需要在工程中引入camera插件

pubspec.yaml中引入插件

  # Camera相机拍照等camera: ^0.10.5+5

处理相机访问权限

在初始化相机控制器时可能会引发权限错误,需要处理这些错误。

  • CameraAccessDenied:当用户拒绝相机访问权限时抛出。

  • CameraAccessDeniedWithoutPrompt:仅限iOS。当用户先前拒绝该权限时抛出。iOS不允许再次提示警报对话框。用户必须进入“设置”>“隐私”>“相机”才能访问相机。

  • CameraAccessRestricted:仅限iOS。当摄像头访问受到限制且用户无法授予权限(家长控制)时抛出。

  • AudioAccessDenied:当用户拒绝音频访问权限时抛出。

  • AudioAccessDeniedWithoutPrompt:目前仅限iOS。当用户先前拒绝该权限时抛出。iOS不允许再次提示警报对话框。用户必须转到“设置”>“隐私”>“麦克风”才能启用音频访问。

  • AudioAccessRestricted:目前仅限iOS。当音频访问受到限制并且用户无法授予权限(家长控制)时抛出。

2.1、camera功能设置

当使用camera时,我们需要设置一些camera的属性内容,比如切换前后摄像头、开启拍照、开启预览、停止预览等。

获取cameras

final cameras = await availableCameras();

camera中使用CameraController来控制相关功能。

设置缩放级别zoomLevel

Future<void> setZoomLevel(double scale) async {await controller!.setZoomLevel(scale);}

切换闪光灯模式

  void onSetFlashModeButtonPressed(FlashMode mode) {setFlashMode(mode).then((_) {if (mounted) {setState(() {});}showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');});}

设置曝光模式

  void onSetExposureModeButtonPressed(ExposureMode mode) {setExposureMode(mode).then((_) {if (mounted) {setState(() {});}showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');});}

设置焦距模式

  void onSetFocusModeButtonPressed(FocusMode mode) {setFocusMode(mode).then((_) {if (mounted) {setState(() {});}showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');});}

开启预览

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

暂停预览

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

切换前后摄像头

void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {if (controller == null) {return;}final CameraController? cameraController = controller;final Offset offset = Offset(details.localPosition.dx / constraints.maxWidth,details.localPosition.dy / constraints.maxHeight,);cameraController?.setExposurePoint(offset);cameraController?.setFocusPoint(offset);}Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {final CameraController cameraController = CameraController(cameraDescription,ResolutionPreset.high,enableAudio: enableAudio,imageFormatGroup: ImageFormatGroup.jpeg,);controller = cameraController;// If the controller is updated then update the UI.cameraController.addListener(() {if (mounted) {setState(() {});}if (cameraController.value.hasError) {print("Camera error ${cameraController.value.errorDescription}");}});try {await cameraController.initialize();await Future.wait(<Future<Object>>[// The exposure mode is currently not supported on the web.cameraController.getMaxZoomLevel().then((double value) => _maxAvailableZoom = value),cameraController.getMinZoomLevel().then((double value) => _minAvailableZoom = value),]);} on CameraException catch (e) {// _showCameraException(e);}setState(() {isCameraStarting = true;});controller!.initialize().then((_) {if (!mounted) {return;}setState(() {isCameraStarting = false;});}).catchError((Object e) {if (e is CameraException) {switch (e.code) {case 'CameraAccessDenied':// Handle access errors here.break;default:// Handle other errors here.break;}}});if (mounted) {setState(() {});}}

上面介绍了一些CameraController的常用设置,当然肯定不全,大致列了几条。

2.2、WidgetsBinding 生命周期改变相机设置

我们自定义Camera,需要在didChangeAppLifecycleState来处理相机。我们需要添加mixin WidgetsBindingObserver

在initState中添加WidgetsBinding.instance?.addObserver(this);

在dispose中移除WidgetsBinding.instance?.removeObserver(this);

这样我们就可以在app的生命周期状态改变时候,更新相机

  @overridevoid 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) {onNewCameraSelected(cameraController.description);}}

2.3、处理预览的画面出现变形的问题

在处理自定义相机功能,我们需要处理预览的画面出现变形的问题。这里我们需要使用CameraPreview。
我们需要使用Transform.scale来进行处理,处理预览的画面出现变形的问题的解决代码如下

Widget buildCameraPreviewWidget(BuildContext context) {final Size size = MediaQuery.of(context).size;final CameraController? cameraController = controller;return Container(width: size.width,height: size.height,child: Stack(alignment: Alignment.center,clipBehavior: Clip.hardEdge,children: [RepaintBoundary(key: _cameraViewGlobalKey,child: Transform.scale(scale: 1.0,// scale: controller!.value.aspectRatio / deviceRatio,alignment: Alignment.center,child: AspectRatio(aspectRatio: size.aspectRatio,child: OverflowBox(alignment: Alignment.center,child: FittedBox(fit: BoxFit.fitHeight,child: SizedBox(width: size.width,height: size.width * cameraController!.value.aspectRatio,child: Stack(fit: StackFit.expand, children: <Widget>[_cameraPreviewWidget(),]),),),),),),),],),);}/// Display the preview from the camera (or a message if the preview is not available).Widget _cameraPreviewWidget() {final CameraController? cameraController = controller;if (cameraController == null || !cameraController.value.isInitialized) {return const Text('cameraController未初始化完成',style: TextStyle(color: Colors.white,fontSize: 24.0,fontWeight: FontWeight.w900,),);} else {return Listener(onPointerDown: (_) => _pointers++,onPointerUp: (_) => _pointers--,child: CameraPreview(controller!,child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {return GestureDetector(behavior: HitTestBehavior.opaque,onScaleStart: _handleScaleStart,onScaleUpdate: _handleScaleUpdate,onTapDown: (TapDownDetails details) =>onViewFinderTap(details, constraints),);}),),);}}

在代码中,我们使用Transform.scale设置为1.0,当设置AspectRatio来设置size.aspectRatio。

2.4、实现拍照功能

在我们代码中,我们使用takePicture来实现拍照,拍照代码如下

Future<void> onTakePicture() async {setState(() {isTaking = true;});takePicture().then((XFile? file) async {if (mounted) {onPausePreview();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);//获取sizeSize size = renderBox.size;// 创建文件pathString 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(),flipHorizontal: isCameraFront,);print("cropImage targetFile:${targetFile}");if (targetFile != null) {selectedImagePath = targetFile.path;// await SaveToAlbumUtil.saveLocalImage(targetFile.path);}setState(() {isHasTakePhoto = true;});} else {// 没有获得图片,重试}setState(() {isTaking = false;});}});}

在裁剪图片中实现如下

import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as IMG;class ImageUtil {//拿到图片的字节数组static Future<ui.Image> loadImageByFile(String path) async {var list = await File(path).readAsBytes();return ImageUtil.loadImageByUInt8List(list);}//通过[Uint8List]获取图片static Future<ui.Image> loadImageByUInt8List(Uint8List list) async {ui.Codec codec = await ui.instantiateImageCodec(list);ui.FrameInfo frame = await codec.getNextFrame();return frame.image;}// 根据GlobalKey来截图Widgetstatic Future<Uint8List?> makeImageUInt8List(GlobalKey globalKey) async {RenderRepaintBoundary boundary =globalKey.currentContext?.findRenderObject() as RenderRepaintBoundary;// 这个可以获取当前设备的像素比var dpr = ui.window.devicePixelRatio;ui.Image image = await boundary.toImage(pixelRatio: dpr);ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);Uint8List? pngBytes = byteData?.buffer.asUint8List();return pngBytes;}static Future<File?> cropSquare(String srcFilePath, String destFilePath, bool flip) async {var bytes = await File(srcFilePath).readAsBytes();IMG.Image? src = IMG.decodeImage(bytes);if (src != null) {var cropSize = min(src.width, src.height);int offsetX = (src.width - min(src.width, src.height)) ~/ 2;int offsetY = (src.height - min(src.width, src.height)) ~/ 2;// IMG.Image destImage = IMG.copyCrop(src, offsetX, offsetY, cropSize, cropSize);IMG.Image destImage = IMG.copyCrop(src,x: offsetX, y: offsetY, width: cropSize, height: cropSize);if (flip) {destImage = IMG.flipVertical(destImage);}var jpg = IMG.encodeJpg(destImage);return await File(destFilePath).writeAsBytes(jpg);} else {throw StateError("cropSquare error");}}static Future<File?> cropImage(String srcFilePath,String destFilePath, {required int x,required int y,required int width,required int height,bool flipVertical = false,bool flipHorizontal = false,}) async {var bytes = await File(srcFilePath).readAsBytes();IMG.Image? src = IMG.decodeImage(bytes);if (src != null) {print("cropImage scr size:(${src.width},${src.height})");IMG.Image destImage = IMG.copyCrop(src,x: x, y: y, width: width, height: height);if (flipVertical) {destImage = IMG.flipVertical(destImage);}if (flipHorizontal) {destImage = IMG.flipHorizontal(destImage);}var jpg = IMG.encodeJpg(destImage);return await File(destFilePath).writeAsBytes(jpg);} else {throw StateError("cropSquare error");}}
}

2.5、拍照完重拍逻辑

当拍照后可能需要重新拍照,这时候我们需要重拍逻辑。

void onRetakeButtonPressed() {setState(() {isHasTakePhoto = false;});selectedImagePath = null;onResumePreview();}Future<void> onResumePreview() async {final CameraController? cameraController = controller;if (cameraController == null || !cameraController.value.isInitialized) {print('Error: select a camera first.');return;}if (cameraController.value.isPreviewPaused) {await cameraController.resumePreview();}}

三、实现自定义相机拍照的功能完整代码

我们实现了实现自定义相机拍照的功能完整代码如下

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.// ignore_for_file: public_member_api_docsimport 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/image_util.dart';
import 'package:flutter_app_demolab/path_util.dart';
import 'dart:ui' as ui;import 'package:flutter_app_demolab/tools/utils/color_util.dart';
import 'package:flutter_app_demolab/tools/utils/time_util.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';class MyCameraPage extends StatefulWidget {const MyCameraPage({super.key,required this.cameras,required this.onSelectedImagePathPressed,});final List<CameraDescription> cameras;final Function(String? selectedImagePath) onSelectedImagePathPressed;@overrideState<MyCameraPage> createState() => _MyCameraPageState();
}class _MyCameraPageState extends State<MyCameraPage>with WidgetsBindingObserver, TickerProviderStateMixin {CameraController? controller;GlobalKey _cameraViewGlobalKey = GlobalKey();GlobalKey _cameraContainerGlobalKey = GlobalKey();bool enableAudio = false;// Counting pointers (number of user fingers on screen)///以下是关于手指缩放画面的变量int _pointers = 0;double _minAvailableZoom = 1.0;double _maxAvailableZoom = 1.0;double _currentScale = 1.0;double _baseScale = 1.0;Size? mediaSize;double? scale;double? defaultZoomLevel;bool isHasTakePhoto = false;bool isCameraFront = true;String? selectedImagePath;bool isTaking = false;bool isCameraStarting = false;@overridevoid initState() {super.initState();// To display the current output from the Camera,// create a CameraController.if (widget.cameras.isNotEmpty && widget.cameras.length >= 2) {controller = CameraController(// Get a specific camera from the list of available cameras.widget.cameras[1],// Define the resolution to use.ResolutionPreset.high,);// Next, initialize the controller. This returns a Future.setState(() {isCameraStarting = true;});controller!.initialize().then((_) {if (!mounted) {return;}setState(() {isCameraStarting = false;});}).catchError((Object e) {if (e is CameraException) {switch (e.code) {case 'CameraAccessDenied':// Handle access errors here.break;default:// Handle other errors here.break;}}});}WidgetsBinding.instance?.addObserver(this);}@overridevoid dispose() {WidgetsBinding.instance?.removeObserver(this);controller?.dispose();super.dispose();}@overridevoid 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) {onNewCameraSelected(cameraController.description);}}final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();@overrideWidget build(BuildContext context) {return Scaffold(key: _scaffoldKey,body: buildCameraContainer(context),);}Widget buildCameraContainer(BuildContext context) {final Size size = MediaQuery.of(context).size;if (widget.cameras.isEmpty) {return Container(width: size.width,height: size.height,decoration: const BoxDecoration(color: Colors.black,),child: Text("未获取到可用的相机,请退出重试。",textAlign: TextAlign.center,maxLines: 2,overflow: TextOverflow.ellipsis,softWrap: true,style: TextStyle(fontSize: 16,fontWeight: FontWeight.w500,fontStyle: FontStyle.normal,color: ColorUtil.hexColor(0xffffff),decoration: TextDecoration.none,),),);} else {return Container(key: _cameraContainerGlobalKey,width: size.width,height: size.height,decoration: const BoxDecoration(color: Colors.black,),child: Stack(alignment: Alignment.center,children: [Column(children: [Expanded(child: buildFutureBuilder(context),)],),buildStackBarWidget(context),],),);}}Widget buildFutureBuilder(BuildContext context) {if (controller != null && controller!.value.isInitialized) {///初始化完成以后,再获取可以缩放画面最大最小的参数mediaSize = MediaQuery.of(context).size;scale = 1 / (controller!.value.aspectRatio * mediaSize!.aspectRatio);controller!.getMaxZoomLevel().then((double value) => _maxAvailableZoom = value);controller!.getMinZoomLevel().then((double value) => _minAvailableZoom = value);return buildCameraPreviewWidget(context);}return const Center(child: CircularProgressIndicator());}Widget buildStackBarWidget(BuildContext context) {final Size size = MediaQuery.of(context).size;double bottomBarHeight = 120;double cameraHeight = size.height - bottomBarHeight;EdgeInsets viewPadding = MediaQuery.of(context).viewPadding;return Container(child: Stack(children: [Positioned(bottom: 0,child: Container(width: size.width,height: bottomBarHeight,color: Colors.transparent,child: Stack(alignment: Alignment.center,children: [Positioned(left: 25,child: buildCloseIcon(context),),buildTakePhotoButton(context),Positioned(right: 25,child: buildRetakeButton(context),),],),),),Positioned(top: viewPadding.top + 25,right: 10,child: buildExchangeButton(context),),],),);}Widget buildCameraPreviewWidget(BuildContext context) {final Size size = MediaQuery.of(context).size;final CameraController? cameraController = controller;return Container(width: size.width,height: size.height,child: Stack(alignment: Alignment.center,clipBehavior: Clip.hardEdge,children: [RepaintBoundary(key: _cameraViewGlobalKey,child: Transform.scale(scale: 1.0,// scale: controller!.value.aspectRatio / deviceRatio,alignment: Alignment.center,child: AspectRatio(aspectRatio: size.aspectRatio,child: OverflowBox(alignment: Alignment.center,child: FittedBox(fit: BoxFit.fitHeight,child: SizedBox(width: size.width,height: size.width * cameraController!.value.aspectRatio,child: Stack(fit: StackFit.expand, children: <Widget>[_cameraPreviewWidget(),]),),),),),),),],),);}/// Display the preview from the camera (or a message if the preview is not available).Widget _cameraPreviewWidget() {final CameraController? cameraController = controller;if (cameraController == null || !cameraController.value.isInitialized) {return const Text('cameraController未初始化完成',style: TextStyle(color: Colors.white,fontSize: 24.0,fontWeight: FontWeight.w900,),);} else {return Listener(onPointerDown: (_) => _pointers++,onPointerUp: (_) => _pointers--,child: CameraPreview(controller!,child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {return GestureDetector(behavior: HitTestBehavior.opaque,onScaleStart: _handleScaleStart,onScaleUpdate: _handleScaleUpdate,onTapDown: (TapDownDetails details) =>onViewFinderTap(details, constraints),);}),),);}}Widget buildCloseIcon(BuildContext context) {return GestureDetector(onTap: () {Navigator.pop(context);},child: Container(color: Colors.transparent,child: Container(width: 50,height: 50,decoration: BoxDecoration(color: Colors.transparent,border: Border.all(color: Colors.transparent,style: BorderStyle.solid,width: 1,),borderRadius: BorderRadius.all(Radius.circular(20)),),child: Icon(Icons.close,size: 30,color: Colors.white,weight: 0.5,),),),);}Widget buildTakePhotoButton(BuildContext context) {return GestureDetector(onTap: () {if (isTaking == false) {if (isHasTakePhoto == true) {widget.onSelectedImagePathPressed(selectedImagePath);Navigator.pop(context);} else {onTakePicturePressed();}}},child: Container(color: Colors.transparent,child: Container(width: 60,height: 60,decoration: const BoxDecoration(color: Colors.transparent,),child: Stack(alignment: Alignment.center,children: [Image.asset("assets/camera/my_take_photo.png",width: 60.0,height: 60.0,fit: BoxFit.contain,),buildHasCheck(context),],),),),);}Widget buildHasCheck(BuildContext context) {if (isTaking == true) {return buildLoading(context);}if (isHasTakePhoto) {return Icon(Icons.check,size: 30,color: Colors.black,weight: 0.5,);}return Container();}Widget buildExchangeButton(BuildContext context) {if (isHasTakePhoto == true) {return Container();}return GestureDetector(onTap: () {onExchangeCameraPressed();},child: Container(color: Colors.transparent,child: Container(width: 50,height: 50,decoration: BoxDecoration(color: Colors.transparent,border: Border.all(color: Colors.transparent,style: BorderStyle.solid,width: 2,),borderRadius: BorderRadius.all(Radius.circular(20)),),child: Container(width: 40,height: 40,decoration: BoxDecoration(color: Colors.transparent,border: Border.all(color: Colors.transparent,style: BorderStyle.solid,width: 5,),borderRadius: BorderRadius.all(Radius.circular(20)),),child: Image.asset("assets/camera/my_exchange_camera.png",width: 50.0,height: 50.0,fit: BoxFit.contain,),),),),);}Widget buildRetakeButton(BuildContext context) {if (isHasTakePhoto == false) {return Container();}return GestureDetector(onTap: () {onRetakeButtonPressed();},child: Container(color: Colors.transparent,child: Container(width: 70,height: 38,alignment: Alignment.center,decoration: BoxDecoration(color: ColorUtil.hexColor(0x000000, alpha: 0.25),border: Border.all(color: Colors.transparent,style: BorderStyle.solid,width: 2,),borderRadius: BorderRadius.all(Radius.circular(5)),),child: Text("重拍",textAlign: TextAlign.center,maxLines: 2,overflow: TextOverflow.ellipsis,softWrap: true,style: TextStyle(fontSize: 16,fontWeight: FontWeight.w500,fontStyle: FontStyle.normal,color: ColorUtil.hexColor(0xffffff),decoration: TextDecoration.none,),),),),);}Widget buildLoading(BuildContext context) {return SizedBox(height: 58,width: 58,child: CircularProgressIndicator(backgroundColor: Colors.grey[200],valueColor: AlwaysStoppedAnimation(Colors.blue),),);}void onRetakeButtonPressed() {setState(() {isHasTakePhoto = false;});selectedImagePath = null;onResumePreview();}Future<void> onPausePreview() async {final CameraController? cameraController = controller;if (cameraController == null || !cameraController.value.isInitialized) {print('Error: select a camera first.');return;}if (!cameraController.value.isPreviewPaused) {await cameraController.pausePreview();}}Future<void> onResumePreview() async {final CameraController? cameraController = controller;if (cameraController == null || !cameraController.value.isInitialized) {print('Error: select a camera first.');return;}if (cameraController.value.isPreviewPaused) {await cameraController.resumePreview();}}Future<void> onExchangeCameraPressed() async {setState(() {isHasTakePhoto = false;});if (isCameraFront == true) {if (widget.cameras.isNotEmpty && widget.cameras.length >= 2) {onNewCameraSelected(widget.cameras[0]);}isCameraFront = false;} else {if (widget.cameras.isNotEmpty && widget.cameras.length >= 2) {onNewCameraSelected(widget.cameras[1]);}isCameraFront = true;}}void onTakePicturePressed() {onTakePicture();}Future<void> onTakePicture() async {setState(() {isTaking = true;});takePicture().then((XFile? file) async {if (mounted) {onPausePreview();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);//获取sizeSize size = renderBox.size;// 创建文件pathString 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(),flipHorizontal: isCameraFront,);print("cropImage targetFile:${targetFile}");if (targetFile != null) {selectedImagePath = targetFile.path;// await SaveToAlbumUtil.saveLocalImage(targetFile.path);}setState(() {isHasTakePhoto = true;});} else {// 没有获得图片,重试}setState(() {isTaking = false;});}});}Future<void> _handleScaleStart(ScaleStartDetails details) async {_baseScale = _currentScale;await controller!.setZoomLevel(_minAvailableZoom);}Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {// When there are not exactly two fingers on screen don't scaleif (controller == null || _pointers != 2) {return;}_currentScale = (_baseScale * details.scale).clamp(_minAvailableZoom, _maxAvailableZoom);await controller!.setZoomLevel(_currentScale);}void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {if (controller == null) {return;}final CameraController? cameraController = controller;final Offset offset = Offset(details.localPosition.dx / constraints.maxWidth,details.localPosition.dy / constraints.maxHeight,);cameraController?.setExposurePoint(offset);cameraController?.setFocusPoint(offset);}Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {final CameraController cameraController = CameraController(cameraDescription,ResolutionPreset.high,enableAudio: enableAudio,imageFormatGroup: ImageFormatGroup.jpeg,);controller = cameraController;// If the controller is updated then update the UI.cameraController.addListener(() {if (mounted) {setState(() {});}if (cameraController.value.hasError) {print("Camera error ${cameraController.value.errorDescription}");}});try {await cameraController.initialize();await Future.wait(<Future<Object>>[// The exposure mode is currently not supported on the web.cameraController.getMaxZoomLevel().then((double value) => _maxAvailableZoom = value),cameraController.getMinZoomLevel().then((double value) => _minAvailableZoom = value),]);} on CameraException catch (e) {// _showCameraException(e);}setState(() {isCameraStarting = true;});controller!.initialize().then((_) {if (!mounted) {return;}setState(() {isCameraStarting = false;});}).catchError((Object e) {if (e is CameraException) {switch (e.code) {case 'CameraAccessDenied':// Handle access errors here.break;default:// Handle other errors here.break;}}});if (mounted) {setState(() {});}}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();return file;} on CameraException catch (e) {print("takePicture CameraException e:${e.toString()}");return null;}}
}

当需要拍照时候,我们调用showModalBottomSheet来打开camera


//显示底部弹窗static void bottomSheetDialog(BuildContext context, Widget widget) {showModalBottomSheet(context: context,isScrollControlled: true,builder: (ctx) {return widget;},);}//返回上一级static void pop(BuildContext context) {Navigator.pop(context);}

打开自定义相机页面


Future<void> testCustomCamera(BuildContext context) async {final cameras = await availableCameras();DialogUtils.bottomSheetDialog(context,MyCameraPage(cameras: cameras,onSelectedImagePathPressed: (String? selectedImagePath) {print("selectedImageFilePath:${selectedImagePath}");if (selectedImagePath != null) {// File imageFile = File(selectedImagePath!);// if (callback != null) {//   callback(imageFile);// }}},),);}

https://brucegwo.blog.csdn.net/article/details/135997096

四、小结

flutter开发实战-Camera自定义相机拍照功能实现

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


文章转载自:
http://carpetbagger.nLcw.cn
http://ecclesiarch.nLcw.cn
http://pepsinogen.nLcw.cn
http://rootless.nLcw.cn
http://physique.nLcw.cn
http://electrorefining.nLcw.cn
http://nobiliary.nLcw.cn
http://ideograph.nLcw.cn
http://juxtapose.nLcw.cn
http://dizygous.nLcw.cn
http://rheochord.nLcw.cn
http://sapiency.nLcw.cn
http://fleam.nLcw.cn
http://inquiline.nLcw.cn
http://pericardiocentesis.nLcw.cn
http://snuggish.nLcw.cn
http://filicoid.nLcw.cn
http://monodist.nLcw.cn
http://countercommercial.nLcw.cn
http://voyager.nLcw.cn
http://bumble.nLcw.cn
http://sympathetic.nLcw.cn
http://truncheon.nLcw.cn
http://guzzler.nLcw.cn
http://ruth.nLcw.cn
http://manhole.nLcw.cn
http://soapwort.nLcw.cn
http://pubes.nLcw.cn
http://favourer.nLcw.cn
http://drawing.nLcw.cn
http://scullery.nLcw.cn
http://transdetermination.nLcw.cn
http://jawed.nLcw.cn
http://unattainable.nLcw.cn
http://contranatural.nLcw.cn
http://rhine.nLcw.cn
http://decubital.nLcw.cn
http://stewardship.nLcw.cn
http://etcetera.nLcw.cn
http://picowatt.nLcw.cn
http://niflheim.nLcw.cn
http://eye.nLcw.cn
http://cartophily.nLcw.cn
http://prowess.nLcw.cn
http://woolhat.nLcw.cn
http://bazooka.nLcw.cn
http://oscar.nLcw.cn
http://smiling.nLcw.cn
http://venereal.nLcw.cn
http://postpituitary.nLcw.cn
http://counterman.nLcw.cn
http://glycan.nLcw.cn
http://jeannette.nLcw.cn
http://bemoan.nLcw.cn
http://molding.nLcw.cn
http://pieman.nLcw.cn
http://aircraft.nLcw.cn
http://bally.nLcw.cn
http://ginny.nLcw.cn
http://sundry.nLcw.cn
http://apiology.nLcw.cn
http://tumid.nLcw.cn
http://unsociability.nLcw.cn
http://volkswil.nLcw.cn
http://splanchnotomy.nLcw.cn
http://underpitch.nLcw.cn
http://unbroken.nLcw.cn
http://adae.nLcw.cn
http://lothsome.nLcw.cn
http://venoclysis.nLcw.cn
http://hurrier.nLcw.cn
http://fumarole.nLcw.cn
http://undoubled.nLcw.cn
http://kirschsteinite.nLcw.cn
http://dct.nLcw.cn
http://axoplasm.nLcw.cn
http://aeroallergen.nLcw.cn
http://bugbane.nLcw.cn
http://miscarry.nLcw.cn
http://senusi.nLcw.cn
http://weird.nLcw.cn
http://flextime.nLcw.cn
http://thanatophobia.nLcw.cn
http://saucerian.nLcw.cn
http://antrum.nLcw.cn
http://glower.nLcw.cn
http://loam.nLcw.cn
http://mavrodaphne.nLcw.cn
http://phanerite.nLcw.cn
http://percurrent.nLcw.cn
http://rile.nLcw.cn
http://psychotechnology.nLcw.cn
http://floatman.nLcw.cn
http://holystone.nLcw.cn
http://haddie.nLcw.cn
http://mica.nLcw.cn
http://schatchen.nLcw.cn
http://dae.nLcw.cn
http://creesh.nLcw.cn
http://elkhound.nLcw.cn
http://www.15wanjia.com/news/103825.html

相关文章:

  • 网站开发招聘简历模板百度指数峰值查询
  • 微信朋友圈的网站连接怎么做关键词优化快速
  • 医疗营销网站建设湖南专业的关键词优化
  • 兰州易天网站建设公司有哪些收录提交入口网址
  • 网页制作布局模板百度seo排名优化公司哪家强
  • 做网购网站杭州seo网站推广排名
  • 潜江网站建设批发口碑营销的方法
  • 在电脑上建设个人网站哈尔滨seo关键词
  • 成立一个网站平台要多少钱百度平台商家app下载
  • 武进网站建设咨询西安网络推广营销公司
  • 石家庄疫情封城最新消息广西网络优化seo
  • 基础网站建设代码seo顾问服务公司
  • 有没有介绍做私家导游的网站网页优化seo广州
  • 镇江网站关键字优化机构厦门人才网唯一官网招聘
  • 郑州网站建设精英全媒体运营师培训机构
  • 北塘网站制作seo优化方案策划书
  • 做网站主要栏目内友情链接平台广告
  • 电商网销东莞seo管理
  • 青岛专业网站制作设计东莞搜索排名提升
  • ubuntu 建设网站图片外链在线生成
  • 国外免费搭建网站源码太原百度关键词优化
  • 新疆免费网站建设seo搜索引擎推广
  • 做网站平台的营业执照电脑培训学校网站
  • 免费动态网站作业模板品牌营销策划案例ppt
  • ai怎么做自己的网站外贸网站建设平台
  • 哪里做公司网站比较好如何制作网页广告
  • 勒流网站建设百度有效点击软件
  • 如何做网站的悬浮窗口群站优化之链轮模式
  • 网站后台管理员密码企业网站建设目标
  • 谷歌有做网站建设整合网络营销