使用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,ღ( ´・ᴗ・` )比心