Flutter:上传图片,选择相机或相册:wechat_assets_picker

图片选择功能:可选单张,或多张。
1、showModalBottomSheet(选择相册/相机)
2、WechatImagePicker(选取图片)
3、CompressMediaFile(图片压缩)

1、ActionSheetUtil

import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:happy/common/index.dart';
import 'package:get/get.dart';/// 底部操作表
/* 使用示例
ActionSheetUtil.showActionSheet(context: context,title: '选择图片',items: [{'id': 1, 'title': '相机', 'type': 'camera'},],onConfirm: (item) {},
);
*/
class ActionSheetUtil {/// 底部操作表/// [context] 上下文/// [title] 标题/// [items] 选项列表 [{'id': 1, 'title': '相机', 'type': 'camera'}]/// [onConfirm] 确认回调 返回选中项static void showActionSheet({required BuildContext context,required String title,required List<Map<String, dynamic>> items,required Function(Map<String, dynamic>) onConfirm,}) {showModalBottomSheet(context: context,backgroundColor: Colors.transparent,builder: (context) => Container(decoration: BoxDecoration(color: AppTheme.pageBgColor,borderRadius: BorderRadius.only(topLeft: Radius.circular(30.w),topRight: Radius.circular(30.w),),),child: SafeArea(child: Column(mainAxisSize: MainAxisSize.min,children: [// 标题Container(height: 100.w,alignment: Alignment.center,child: TextWidget.body(title,size: 30.sp,weight: FontWeight.w600,color: AppTheme.color000,),),// 选项列表...items.map((item) => GestureDetector(onTap: () {Navigator.pop(context);onConfirm(item);},child: Container(height: 100.w,alignment: Alignment.center,decoration: BoxDecoration(border: Border(top: BorderSide(color: AppTheme.dividerColor,width: 1,),),),child: TextWidget.body(item['title'],size: 28.sp,color: AppTheme.color000,),),)),// 间隔Container(height: 16.w,color: AppTheme.dividerColor,),// 取消按钮GestureDetector(onTap: () => Navigator.pop(context),child: Container(height: 100.w,alignment: Alignment.center,color: Colors.transparent,child: TextWidget.body('取消'.tr,size: 28.sp,color: AppTheme.color000,),),),],),),),);}
}

在这里插入图片描述

2、WechatImagePicker

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:happy/common/index.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';/// 微信风格图片选择器封装
class WechatImagePicker {/// 显示图片选择弹窗(相机 + 相册)/// 选择图片后自动压缩,返回压缩后的文件/// [maxAssets] 最大选择数量,1为单选,>1为多选/// [onSingleResult] 单张图片选择回调/// [onMultiResult] 多张图片选择回调static void showImagePicker({required BuildContext context,Function(File?)? onSingleResult,Function(List<File>)? onMultiResult,int maxAssets = 1,bool autoCompress = true,}) {// 验证回调参数if (maxAssets == 1 && onSingleResult == null) {throw ArgumentError('单张选择时必须提供 onSingleResult 回调');}if (maxAssets > 1 && onMultiResult == null) {throw ArgumentError('多张选择时必须提供 onMultiResult 回调');}List<Map<String, dynamic>> actions = [];// 单张选择时显示相机和相册选项if (maxAssets == 1) {actions = [{"id": 1, "title": "相机".tr, "type": "camera"},{"id": 2, "title": "相册".tr, "type": "gallery"},];} else {// 多张选择时只显示相册选项actions = [{"id": 1, "title": "相册选择($maxAssets张)".tr, "type": "gallery"},];}ActionSheetUtil.showActionSheet(context: context,title: '请选择'.tr,items: actions,onConfirm: (item) async {try {if (item['type'] == 'camera') {// 相机拍照(仅单张)final selectedFile = await _pickFromCamera(context);if (selectedFile != null && autoCompress) {final compressedFile = await _compressImage(selectedFile);onSingleResult!(compressedFile);} else {onSingleResult!(selectedFile);}} else if (item['type'] == 'gallery') {if (maxAssets == 1) {// 单张相册选择final selectedFile = await _pickFromGallery(context);if (selectedFile != null && autoCompress) {final compressedFile = await _compressImage(selectedFile);onSingleResult!(compressedFile);} else {onSingleResult!(selectedFile);}} else {// 多张相册选择final selectedFiles = await _pickMultipleFromGallery(context, maxAssets);if (autoCompress && selectedFiles.isNotEmpty) {final compressedFiles = await _compressMultipleImages(selectedFiles);onMultiResult!(compressedFiles);} else {onMultiResult!(selectedFiles);}}}} catch (e) {print('图片选择异常: $e');if (maxAssets == 1) {onSingleResult!(null);} else {onMultiResult!([]);}}},);}/// 直接从相机拍照static Future<File?> _pickFromCamera(BuildContext context) async {try {final AssetEntity? entity = await CameraPicker.pickFromCamera(context,pickerConfig: CameraPickerConfig(enableRecording: false,enableAudio: false,enableSetExposure: true,enableExposureControlOnPoint: true,enablePinchToZoom: true,shouldDeletePreviewFile: true,maximumRecordingDuration: const Duration(seconds: 15),),);if (entity != null) {final File? file = await entity.file;if (file != null && await file.exists()) {print('相机拍照成功: ${file.path}');return file;}}return null;} catch (e) {print('相机拍照失败: $e');Loading.toast('相机拍照失败'.tr);return null;}}/// 直接从相册选择static Future<File?> _pickFromGallery(BuildContext context) async {try {final List<AssetEntity>? assets = await AssetPicker.pickAssets(context,pickerConfig: AssetPickerConfig(maxAssets: 1,requestType: RequestType.image,themeColor: Theme.of(context).primaryColor,textDelegate: const AssetPickerTextDelegate(),),);if (assets != null && assets.isNotEmpty) {final File? file = await assets.first.file;if (file != null && await file.exists()) {print('相册选择成功: ${file.path}');return file;}}return null;} catch (e) {print('相册选择失败: $e');if (e.toString().contains('permission')) {Loading.toast('请允许访问相册权限'.tr);} else {Loading.toast('相册选择失败'.tr);}return null;}}/// 选择多张图片(仅相册)static Future<List<File>> pickMultipleImages(BuildContext context, {int maxAssets = 9,}) async {try {final List<AssetEntity>? assets = await AssetPicker.pickAssets(context,pickerConfig: AssetPickerConfig(maxAssets: maxAssets,requestType: RequestType.image,themeColor: Theme.of(context).primaryColor,textDelegate: const AssetPickerTextDelegate(),),);if (assets != null && assets.isNotEmpty) {final List<File> files = [];for (final asset in assets) {final File? file = await asset.file;if (file != null && await file.exists()) {files.add(file);}}return files;}return [];} catch (e) {print('多图片选择失败: $e');Loading.toast('图片选择失败'.tr);return [];}}/// 从相册选择多张图片static Future<List<File>> _pickMultipleFromGallery(BuildContext context, int maxAssets) async {try {final List<AssetEntity>? assets = await AssetPicker.pickAssets(context,pickerConfig: AssetPickerConfig(maxAssets: maxAssets,requestType: RequestType.image,themeColor: Theme.of(context).primaryColor,textDelegate: const AssetPickerTextDelegate(),),);if (assets != null && assets.isNotEmpty) {final List<File> files = [];for (final asset in assets) {final File? file = await asset.file;if (file != null && await file.exists()) {files.add(file);}}print('相册选择成功: ${files.length}张图片');return files;}return [];} catch (e) {print('多图片选择失败: $e');if (e.toString().contains('permission')) {Loading.toast('请允许访问相册权限'.tr);} else {Loading.toast('图片选择失败'.tr);}return [];}}/// 压缩多张图片static Future<List<File>> _compressMultipleImages(List<File> originalFiles) async {final List<File> compressedFiles = [];for (int i = 0; i < originalFiles.length; i++) {final originalFile = originalFiles[i];print('压缩第${i + 1}/${originalFiles.length}张图片');final compressedFile = await _compressImage(originalFile);if (compressedFile != null) {compressedFiles.add(compressedFile);}}print('批量压缩完成: ${compressedFiles.length}/${originalFiles.length}张');return compressedFiles;}/// 压缩图片static Future<File?> _compressImage(File originalFile) async {try {print('开始压缩图片: ${originalFile.path}');final compressedFile = await DuCompress.image(originalFile.path);if (compressedFile == null) {print('图片压缩失败,返回原文件');return originalFile;}final File compressedImageFile = File(compressedFile.path);if (await compressedImageFile.exists()) {final originalSize = await originalFile.length();final compressedSize = await compressedImageFile.length();print('图片压缩成功: ${originalSize}KB -> ${compressedSize}KB');return compressedImageFile;} else {print('压缩后的文件不存在,返回原文件');return originalFile;}} catch (e) {print('图片压缩异常: $e,返回原文件');return originalFile;}}
}

在这里插入图片描述

3、CompressMediaFile

import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:video_compress/video_compress.dart';/// 压缩工具类
/* 使用示例
DuCompress.image('图片路径');
DuCompress.video('视频路径');
final file = await ImagePicker().pickImage(source: ImageSource.gallery);
if (file == null) return;
// 创建文件对象
File originalFile = File(file.path);
// 压缩图片
var newFile = await DuCompress.image(originalFile.path);
if (newFile == null) return;
// 将 XFile 转换为 File
File compressedFile = File(newFile.path);
// 上传压缩后的图片
ChatApi.uploadFile(compressedFile);
*//// 压缩返回类型
class CompressMediaFile {final File? thumbnail;final MediaInfo? video;CompressMediaFile({this.thumbnail,this.video,});
}/// 媒体压缩
class DuCompress {// 压缩图片static Future<XFile?> image(String path, {int minWidth = 1920,int minHeight = 1080,}) async {return await FlutterImageCompress.compressAndGetFile(path,'${path}_temp.jpg',keepExif: true,quality: 70,format: CompressFormat.jpeg,minHeight: minHeight,minWidth: minWidth,);}/// 压缩视频static Future<CompressMediaFile> video(File file) async {var result = await Future.wait([// 1 视频压缩VideoCompress.compressVideo(file.path,quality: VideoQuality.Res640x480Quality,deleteOrigin: false, // 默认不要去删除原视频includeAudio: true,frameRate: 25,),// 2 视频缩略图VideoCompress.getFileThumbnail(file.path,quality: 80,position: -1000,),]);return CompressMediaFile(video: result.first as MediaInfo,thumbnail: result.last as File,);}/// 清理缓存static Future<bool?> clean() async {return await VideoCompress.deleteAllCache();}/// 取消static Future<void> cancel() async {await VideoCompress.cancelCompression();}
}

使用示例

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:happy/common/utils/wechat_image_picker.dart';/// WechatImagePicker 使用示例
class WechatImagePickerExample {/// 示例1:选择单张图片(自动压缩)static void pickSingleImage(BuildContext context) {WechatImagePicker.showImagePicker(context: context,maxAssets: 1, // 单张选择autoCompress: true, // 自动压缩onSingleResult: (File? compressedFile) {if (compressedFile != null) {print('单张图片选择成功: ${compressedFile.path}');// 这里处理选择的图片,已经是压缩后的} else {print('用户取消选择或选择失败');}},);}/// 示例2:选择多张图片(最多9张,自动压缩)static void pickMultipleImages(BuildContext context) {WechatImagePicker.showImagePicker(context: context,maxAssets: 9, // 最多选择9张autoCompress: true, // 自动压缩onMultiResult: (List<File> compressedFiles) {if (compressedFiles.isNotEmpty) {print('多张图片选择成功: ${compressedFiles.length}张');for (int i = 0; i < compressedFiles.length; i++) {print('图片${i + 1}: ${compressedFiles[i].path}');}// 这里处理选择的图片列表,都是压缩后的} else {print('用户取消选择或选择失败');}},);}/// 示例3:选择单张图片(不压缩)static void pickSingleImageWithoutCompress(BuildContext context) {WechatImagePicker.showImagePicker(context: context,maxAssets: 1,autoCompress: false, // 不压缩onSingleResult: (File? originalFile) {if (originalFile != null) {print('单张原图选择成功: ${originalFile.path}');// 这里处理原始图片}},);}/// 示例4:选择多张图片(最多3张,不压缩)static void pickMultipleImagesWithoutCompress(BuildContext context) {WechatImagePicker.showImagePicker(context: context,maxAssets: 3, // 最多选择3张autoCompress: false, // 不压缩onMultiResult: (List<File> originalFiles) {if (originalFiles.isNotEmpty) {print('多张原图选择成功: ${originalFiles.length}张');// 这里处理原始图片列表}},);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89488.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89488.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytest--0

1 pytest 使用方式 pytest测试框架-- 基本功能使用详解 2 pytest-mock常用方式 pytest–1–pytest-mock常用的方法 3

multiprocessing.Pool 中的 pickle 详解

前言&#xff1a; 在 Python 的 multiprocessing.Pool 中&#xff0c;任务和数据需要通过序列化&#xff08;pickle&#xff09;传递给子进程。pickle 是 Python 的内置序列化模块&#xff0c;用于将 Python 对象转换为字节流&#xff0c;以便在进程间通信时传递。然而&#xf…

Java集合框架体系详解:List/Set/Map接口对比与核心实现原理

一、集合框架核心接口对比 1.1 List/Set/Map接口特性接口类型特性描述典型实现List有序可重复&#xff0c;支持索引访问ArrayList/LinkedListSet无序不可重复&#xff0c;基于哈希表或树实现HashSet/TreeSetMap键值对存储&#xff0c;键唯一值可重复HashMap/TreeMap核心差异&am…

LeafletJS 进阶:GeoJSON 与动态数据可视化

引言 LeafletJS 作为一个轻量、灵活的 JavaScript 地图库&#xff0c;以其对 GeoJSON 数据格式的强大支持而闻名。GeoJSON 是一种基于 JSON 的地理数据格式&#xff0c;能够表示点&#xff08;Point&#xff09;、线&#xff08;LineString&#xff09;、多边形&#xff08;Po…

【STM32实践篇】:F407 时钟系统

文章目录1. 时钟与启动2. CubeMX 时钟树2.1 时钟源2.2 PLL 锁相环2.3 时钟分发与选择2.4 频率限制1. 时钟与启动 复位默认时钟&#xff1a;系统复位后&#xff0c;CPU 时钟默认由 16MHz 内部 RC 振荡器&#xff08;HSI&#xff09;提供&#xff0c;该 RC 振荡器经工厂校准&…

纯前端html实现图片坐标与尺寸(XY坐标及宽高)获取

纯前端html实现图片坐标与尺寸&#xff08;XY坐标及宽高&#xff09;获取。用于证书图片或pdf打印的坐标测定。 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <title>纯html前端实现图片坐标与尺寸&am…

飞睿UWB超宽带定位测距技术,数字钥匙重塑智能生活,高精度厘米级定位无感解锁

最近&#xff0c;数字钥匙领域动作频频&#xff0c;科技巨头与车企正掀起一波创新浪潮。小米15S Pro搭载恩智浦UWB芯片&#xff0c;用户靠近闸机即可无感通行深圳云巴一号线&#xff0c;轻触小米YU7车门自动解锁&#xff0c;实现手机-汽车-公共交通的无缝数字钥匙生态。在智能家…

基于springboot+vue+mysql平台的医疗病历交互系统(源码+论文)

一、开发环境 相关技术介绍 B/S模式分析 C/S模式&#xff1a;主要由客户应用程序(Client)、服务器管理程序(Server)和中间件(middleware)三个部件组成。客户应用程序是系统中用户与数据组件交互。服务器程序负责系统资源&#xff0c;如管理信息数据库的有效管理。中间件负责连…

arm架构,arm内核,处理器之间的关系

一、情景分析 我们经常说&#xff0c;stm32f103是采用cotex-M3内核&#xff0c;基于armv7架构设计的。 那么&#xff0c;stm32f103、cotex-M3、armv7之间有什么关系呢&#xff1f; 二、层次分析 1. 架构&#xff08;Architecture&#xff09; 定义&#xff1a;架构是处理器…

基于PHP的招投标系统_603gk

目录具体实现截图课程项目技术路线开发技术介绍PHP核心代码部分展示系统测试详细视频演示/源码获取具体实现截图 课程项目技术路线 招投标系统后端采用 PHP 语言搭配Thinkphp或者 Laravel 框架&#xff0c;PHP 语法简洁且功能强大&#xff0c;Laravel 或者Thinkphp框架能优化代…

深入解析 JavaScript 中的 `$.ajax()`:专业指南与实战示例

文章目录一、为什么需要 $.ajax()&#xff1f;二、核心语法解析三、关键参数深度剖析四、实战示例&#xff1a;从基础到进阶五、错误处理最佳实践六、性能与安全优化七、现代替代方案对比八、总结作为网站编辑&#xff0c;我将带您深入剖析 jQuery 的 $.ajax() 方法。本文不仅涵…

Flutter 前端开发中的常见问题全面解析

Flutter 开发中的常见问题全面解析一篇给 Flutter 开发者「灵儿」里里外外都能看的问题项。从基础开发到打包上线&#xff0c;每一步都充满坑&#xff0c;我们详细列出「环环盗光」的那些场景和解决思路&#xff01;【基础系统】开发环境问题 1. flutter doctor 报错 常见错误:…

STM32 单片机的停车场管理系统设计与实现

基于 STM32 的停车场管理系统设计与实现摘要随着城市汽车保有量的快速增长&#xff0c;停车场管理的效率与智能化水平愈发重要。本文设计并实现了一套基于 STM32 单片机的停车场管理系统&#xff0c;整合车辆检测、车位引导、计费管理及信息交互等功能。系统以 STM32 为控制核心…

STM32 写选项字 关键要加载HAL_FLASH_OB_Launch

AI乱写&#xff0c;还是得自己来&#xff01;void Write_OptionBytes_IWDG_STDBY(void) {FLASH_OBProgramInitTypeDef OBInit;HAL_FLASHEx_OBGetConfig(&OBInit); // 获取当前选项字节配置[6,7](ref)// 检查当前nRST_STDBY位&#xff08;IWDG_STDBY相关位&#xff09;是否…

153.在 Vue 3 中使用 OpenLayers + Cesium 实现 2D/3D 地图切换效果

&#x1f3ac; 效果演示截图 ✨ 前言 在实际项目开发中&#xff0c;我们经常需要提供「二维地图 三维地形」的可视化效果切换&#xff0c;例如&#xff1a; 智慧农业展示耕地分布 三维地形起伏&#xff1b; 智慧城市展示建筑物点位 三维城市&#xff1b; 数字孪生场景中&…

纯C++11实现!零依赖贝叶斯情感分析系统,掌握机器学习系统工程化的秘密!

本文深度剖析了一个完全基于C++11标准库实现的贝叶斯情感分析系统。该系统采用模块化设计,实现了从文本预处理、特征提取到朴素贝叶斯分类的完整机器学习流水线。 1. 系统架构概览 1.1 技术栈选择与设计哲学 该系统完全采用C++11标准库实现,无任何外部依赖,体现了"纯…

Android原生Dialog

在原生android里面&#xff0c;有两种dialog写法&#xff0c;一种是直接使用里面提供的AlertDialog.Builder方法去使用&#xff0c;另一种是我们自己根据自己的ui来设计&#xff08;自定义&#xff09;。在一般开发中&#xff0c;我们主要使用的是自定义&#xff0c;主要是Aler…

Nacos 开源 MCP Router,加速 MCP 私有化部署

作者&#xff1a;正己 Nacos MCP Router 简介 Nacos MCP Router 是一个基于 MCP 官方 SDK 开发的标准 MCP Server&#xff0c;为 MCP Client 提供 MCP Server 的智能搜索、安装、代理等功能&#xff0c;极大地简化了 MCP 服务的使用流程。同时&#xff0c;Nacos MCP Router 跟…

【赵渝强老师】Redis的主从复制集群

Redis的主从复制是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为Master主节点&#xff0c;后者称为Slave从节点。数据的复制是单向的&#xff0c;只能由主节点到从节点。在默认情况下每台Redis服务器都是主节点。一个主节点可以有多个从节点或者没有…

Git 子模块只更新部分模块的问题排查总结

Git 子模块只更新部分模块的问题排查总结 问题描述 在执行 git submodule update --init --recursive 命令时&#xff0c;虽然 .gitmodules 文件中定义了 3 个子模块&#xff0c;但只有 handy-ollama 被更新&#xff0c;其他两个子模块没有被处理。 > git submodule upda…