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
英文地址,請注明出處:http://en.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…