使用FLutter開(kāi)發(fā)了一個(gè)圖片選擇的組件凌盯,功能如下:
1付枫、支持設(shè)置最大可選圖片的個(gè)數(shù);
2驰怎、根據(jù)圖片個(gè)數(shù)自適應(yīng)容器組件的高度阐滩;
3、可設(shè)置容器的最大高度县忌;
4掂榔、支持點(diǎn)擊放大和刪除功能;
具體效果如下
使用到的三方插件:
get: ^4.6.6
#路由管理
flutter_easyloading: ^3.0.5
#加載動(dòng)畫(huà)症杏、彈框
image_picker: ^1.0.4
#圖片選擇器
photo_view: ^0.14.0
#點(diǎn)擊圖片處理--放大装获,縮放、滑動(dòng)
代碼如下:
1鸳慈、先添加相冊(cè)饱溢、相機(jī)權(quán)限
iOS
<key>NSCameraUsageDescription</key>
<string>更換個(gè)人頭像需要使用相機(jī)功能</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>更換個(gè)人頭像需要使用相冊(cè)功能</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>更換個(gè)人頭像需要使用相冊(cè)功能</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權(quán)限需要使用READ_MEDIA_IMAGE、READ_MEDIA_VIDEO走芋、READ_MEDIA_AUDIO 來(lái)替代適配 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
2绩郎、創(chuàng)建一個(gè)ImagePickerMul
的類(lèi)
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; //最大選擇圖片數(shù)量
final double maxHeight; //圖片容器的最大高度
final Function(List<XFile>) pickerImgCallBack; //選取圖片成功之后拿到返回結(jié)果
const ImagePickerMul({
super.key,
this.maxCount = 1000,
this.maxHeight = 300.0,
required this.pickerImgCallBack,
});
@override
State<ImagePickerMul> createState() => _ImagePickerMulState();
}
class _ImagePickerMulState extends State<ImagePickerMul> {
final List<XFile> _imageFileList = []; //存放圖片的數(shù)組
final ImagePicker _picker = ImagePicker();
dynamic _pickerImageError;
int _bigImageIndex = 0; //存放需要放大的圖片下標(biāo)
// 獲取當(dāng)前展示圖的數(shù)量
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) {
//加上新選的不能超過(guò)總數(shù)量
for (var element in pickedFileList) {
_imageFileList.add(element);
}
} else {
EasyLoading.showToast(
'最多只能選取${widget.maxCount}張圖片,多余的圖片將會(huì)自動(dòng)刪除翁逞!',
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;
});
//點(diǎn)擊圖片放大
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;
}
}
@override
Widget 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 {
//顯示添加符號(hào)
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) {},
),
),
本人將會(huì)持續(xù)更新在開(kāi)發(fā)過(guò)程中遇到的各種小demo,如果喜歡的話挖函,歡迎給個(gè)star状植,?( ′???` )比心