Flutter开发之图片选择器

使用FLutter开发了一个图片选择的组件,功能如下:

1、支持设置最大可选图片的个数;
2、根据选择的图片个数自适应容器组件的高度;
3、可设置容器的最大高度;
4、支持点击放大和删除功能;

具体效果如下

请添加图片描述

使用到的三方插件:

get: ^4.6.6 #路由管理
flutter_easyloading: ^3.0.5 #加载动画、弹框
image_picker: ^1.0.4 #图片选择器
photo_view: ^0.14.0 #点击图片处理–放大,缩放、滑动

代码如下:

1、先添加相册、相机权限

iOS

	<key>NSCameraUsageDescription</key><string>更换个人头像需要使用相机功能</string><key>NSPhotoLibraryAddUsageDescription</key><string>更换个人头像需要使用相册功能</string><key>NSPhotoLibraryUsageDescription</key><string>更换个人头像需要使用相册功能</string>

Android

    <!-- 摄像头 --><uses-permission android:name="android.permission.CAMERA" /><!-- 文件读取 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/><!--    Android 13 READ_EXTERNAL_STORAGE权限需要使用READ_MEDIA_IMAGE、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 来替代适配 --><uses-permission android:name="android.permission.READ_MEDIA_IMAGE" /><uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

2、创建一个ImagePickerMul的类

import 'dart:io';import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:photo_view/photo_view.dart';class ImagePickerMul extends StatefulWidget {final int maxCount; //最大选择图片数量final double maxHeight; //图片容器的最大高度final Function(List<XFile>) pickerImgCallBack; //选取图片成功之后拿到返回结果const ImagePickerMul({super.key,this.maxCount = 1000,this.maxHeight = 300.0,required this.pickerImgCallBack,});@overrideState<ImagePickerMul> createState() => _ImagePickerMulState();
}class _ImagePickerMulState extends State<ImagePickerMul> {final List<XFile> _imageFileList = []; //存放图片的数组final ImagePicker _picker = ImagePicker();dynamic _pickerImageError;int _bigImageIndex = 0; //存放需要放大的图片下标// 获取当前展示图的数量int getImageCount() {widget.pickerImgCallBack(_imageFileList);if (_imageFileList.length < widget.maxCount) {return _imageFileList.length + 1;} else {return _imageFileList.length;}}void _onImageButtonPressed(ImageSource source, {double? maxHeight,double? maxWidth,int? imageQuality,}) async {try {final pickedFileList = await _picker.pickMultipleMedia(maxHeight: maxHeight,maxWidth: maxWidth,imageQuality: imageQuality,);setState(() {if (_imageFileList.length < widget.maxCount) {if ((_imageFileList.length + pickedFileList.length) <=widget.maxCount) {//加上新选的不能超过总数量for (var element in pickedFileList) {_imageFileList.add(element);}} else {EasyLoading.showToast('最多只能选取${widget.maxCount}张图片,多余的图片将会自动删除!',duration: const Duration(milliseconds: 1500),);int avaliableCount = widget.maxCount - _imageFileList.length;for (int i = 0; i < avaliableCount; i++) {_imageFileList.add(pickedFileList[i]);}}}});} catch (e) {EasyLoading.showToast('$_pickerImageError');_pickerImageError = e;}}void _removeImage(int index) {setState(() {_imageFileList.removeAt(index);});}void _showBigImage(int index) {setState(() {_bigImageIndex = index;});//点击图片放大showDialog(context: context,builder: (context) {return Dialog(insetPadding: const EdgeInsets.only(left: 0.0),child: PhotoView(tightMode: true,imageProvider: FileImage(File(_imageFileList[_bigImageIndex].path,),),),);},);}Widget? displayBigImage() {if (_imageFileList.length > _bigImageIndex) {return Image.file(File(_imageFileList[_bigImageIndex].path,),fit: BoxFit.cover,);} else {return null;}}@overrideWidget build(BuildContext context) {int columnCount = 0;if (_imageFileList.length == widget.maxCount) {columnCount = (widget.maxCount / 3).ceil();} else {columnCount = ((_imageFileList.length + 1) / 3).ceil();}return _imageFileList.isNotEmpty? Container(height: columnCount * (Get.width / 3),constraints: BoxConstraints(maxHeight: widget.maxHeight,),child: GridView.builder(gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,childAspectRatio: 1.0,),itemBuilder: (context, index) {if (_imageFileList.length < widget.maxCount) {if (index < _imageFileList.length) {return Padding(padding: const EdgeInsets.all(10.0),child: Stack(alignment: Alignment.center,children: [Positioned(top: 0.0,left: 0.0,bottom: 0.0,right: 0.0,child: GestureDetector(child: Image.file(File(_imageFileList[index].path,),fit: BoxFit.cover,),onTap: () => _showBigImage(index),),),Positioned(top: 0.0,right: 0.0,width: 20,height: 20,child: GestureDetector(child: const Icon(Icons.close,color: Colors.white,),onTap: () => _removeImage(index),),),],),);} else {//显示添加符号return Padding(padding: const EdgeInsets.all(10.0),child: IconButton(onPressed: () => _onImageButtonPressed(ImageSource.gallery,imageQuality: 40, //图片压缩),icon: const Icon(Icons.add_a_photo_outlined),),);}} else {//选满了return Container(padding: const EdgeInsets.all(10.0),child: Stack(alignment: Alignment.center,children: [Positioned(top: 0.0,left: 0.0,bottom: 0.0,right: 0.0,child: GestureDetector(child: Image.file(File(_imageFileList[index].path,),fit: BoxFit.cover,),onTap: () => _showBigImage(index),),),Positioned(top: 0.0,right: 0.0,width: 20,height: 20,child: GestureDetector(child: const SizedBox(child: Icon(Icons.close,color: Colors.white,),),onTap: () => _removeImage(index),),),],),);}},itemCount: getImageCount(),),): SizedBox(width: 90,height: 90,child: IconButton(onPressed: () => _onImageButtonPressed(ImageSource.gallery,imageQuality: 40, //图片压缩),icon: const Icon(Icons.add_a_photo_outlined),),);}
}

3、使用

body: Container(color: Colors.yellow,child: ImagePickerMul(maxCount: 9,maxHeight: 300.0,pickerImgCallBack: (imageList) {},),),

本人将会持续更新在开发过程中遇到的各种小demo,如果喜欢的话,欢迎给个star,ღ( ´・ᴗ・` )比心

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

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

相关文章

KIMI官方精选提示词,好牛的感觉啊啊啊!

晚上好&#xff0c;我是云桃桃。一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃 1枚程序媛&#xff0c;大专生&#xff0c;2年时间从1800到月入过万&#xff0c;工作5年买房。 分享成长心得。 255篇原创内容-公众号 后台回复“前端工具”可获取开发工具&#xff0…

剑指offer打卡 JZ8 二叉树的下一个结点

在牛客网刷的&#xff0c;还是跟leetcode一样非acm模式&#xff0c;由于急着暑期实习题量不固定&#xff0c;八股算法轮刷 打卡内容偏个人笔记&#xff0c;本人水平一般(代码随想录稀里糊涂刷了一遍)&#xff0c;从小白开始分析(甚至会分析语法)&#xff0c;尽量一题多解深入探…

Day28 代码随想录(1刷) 回溯

491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情况…

SV学习笔记(一)

SV&#xff1a;SystemVerilog 开启SV之路 数据类型 內建数据类型 四状态与双状态 &#xff1a; 四状态指0、1、X、Z&#xff0c;包括logic、integer、 reg、 wire。双状态指0、1&#xff0c;包括bit、byte、 shortint、int、longint。 有符号与无符号 &#xff1a; 有符号&am…

【JavaEE】_Spring MVC项目上传文件

目录 1. 文件上传具体实现 2. 保存文件 1. 文件上传具体实现 .java文件内容如下&#xff1a; package com.example.demo.controller;import com.example.demo.Person; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.Multip…

小剧场短剧影视小程序源码,附带系统搭建教程

安装教程 linux/win任选 PHP版本&#xff1a;7.3/7.2&#xff08;测试时我用的7.2要安装sg扩展 不会的加QQ295526639&#xff09; 批量替换域名http://video.owoii.com更换为你的 批量替换域名http://120.79.77.163:1更换为你的 这两个都替换你的 /extend/yzf/lib/epay.config.…

H5抓包——Android 使用电脑浏览器 DevTools调试WebView

H5抓包——Android 使用电脑浏览器 DevTools调试WebView 一、使用步骤 1、电脑通过数据线连接手机&#xff0c;开启USB调试&#xff08;打开手机开发者选项&#xff09; 2、打开待调试的H5 App&#xff0c;进入H5界面 3、打开电脑浏览器&#xff0c;调试界面入口 如果用ed…

什么是域名中介?

域名中介是指在买家和卖家之间提供交易服务的中间人或机构&#xff0c;专门负责协助双方完成域名的买卖过程。域名中介服务通常包括但不限于以下几个方面&#xff1a; 1.价格谈判&#xff1a;协助买卖双方就域名的价格达成一致&#xff0c;帮助解决谈判中可能出现的疑难问题。 …

自动驾驶的世界模型:综述

自动驾驶的世界模型&#xff1a;综述 附赠自动驾驶学习资料和量产经验&#xff1a;链接 24年3月澳门大学和夏威夷大学的论文“World Models for Autonomous Driving: An Initial Survey”。 在快速发展的自动驾驶领域&#xff0c;准确预测未来事件并评估其影响的能力对安全性…

《QT实用小工具·七》CPU内存显示控件

1、概述 源码放在文章末尾 CPU内存显示控件 项目包含的功能如下&#xff1a; 实时显示当前CPU占用率。实时显示内存使用情况。包括共多少内存、已使用多少内存。全平台通用&#xff0c;包括windows、linux、ARM。发出信号通知占用率和内存使用情况等&#xff0c;以便自行显示…

【xinference】(8):在autodl上,使用xinference部署qwen1.5大模型,速度特别快,同时还支持函数调用,测试成功!

1&#xff0c;关于xinference Xorbits Inference (Xinference) 是一个开源平台&#xff0c;用于简化各种 AI 模型的运行和集成。借助 Xinference&#xff0c;您可以使用任何开源 LLM、嵌入模型和多模态模型在云端或本地环境中运行推理&#xff0c;并创建强大的 AI 应用。 Xor…

UGUI 进阶

UI事件监听接口 目前所有的控件都只提供了常用的事件监听列表 如果想做一些类似长按&#xff0c;双击&#xff0c;拖拽等功能是无法制作的 或者想让Image和Text&#xff0c;RawImage三大基础控件能够响应玩家输入也是无法制作的 而事件接口就是用来处理类似问题 让所有控件都…