在最近的開發中,需要做一個選擇圖片(包括拍照和相冊選擇)然後上傳的功能,我們的項目是iOS原生和flutter混編的,首先用flutter實現這個頁面,選擇了第三方插件image_picker,下面先看一下效果圖
下面我們開始一步一步實現這個頁面的邏輯,核心是在實現一個可複用的圖片選擇控件,支持設置最大選擇圖片數maxCount,支持刪除。
第一步:集成image_picker ,導入圖片資源(就是導入那個相機的icon和刪除的icon這裏就不展開說了)
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
image_picker: 0.6.0+8
我這裏用的是0.6.0+8版本,可以自行選擇最新版本,然後以iOS爲例,需要添加訪問相冊的權限
Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist
:
NSPhotoLibraryUsageDescription
- describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor.NSCameraUsageDescription
- describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.NSMicrophoneUsageDescription
- describe why your app needs access to the microphone, if you intend to record videos. This is called Privacy - Microphone Usage Description in the visual editor
然後再用到的頁面中 import 'package:image_picker/image_picker.dart';
第二步:根據需求封裝一個圖片選擇控件,我這裏是一行三張圖片的佈局,刪除按鈕在右上角
首先分析一下需求:
1.支持兩種樣式,有圖片或者上傳樣式
2.在有圖片的樣式時,才顯示右上角的刪除icon
3.在上傳樣式時,點擊彈出一個選擇相機/相冊的菜單(iOS中的ActionSheet)
4.圖片樣式時,點擊預覽大圖(這個還沒實現,後續有時間再更新)
需求理清晰了就可以開始擼碼:
class UploadImageItem extends StatelessWidget {
final GestureTapCallback onTap;
final Function callBack;
final UploadImageModel imageModel;
final Function deleteFun;
UploadImageItem({this.onTap, this.callBack, this.imageModel, this.deleteFun});
@override
Widget build(BuildContext context) {
return Container(
width: 115,
height: 115,
child: Stack(
alignment: Alignment.topRight,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 8, right: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: Color(0xFFF0F0F0)),
child: imageModel == null
? InkWell(
onTap: onTap ??
() {
BottomActionSheet.show(context, [
'相機',
'相冊',
], callBack: (i) {
callBack(i);
return;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Image.asset(
'resources/image_picker.png',
),
),
Text(
'上傳',
style: TextStyle(
fontSize: 12, color: Color(0xff999999)),
)
],
))
: Image.file(
imageModel.imageFile,
width: 105,
height: 105,
)),
Offstage(
offstage: (imageModel == null),
child: InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Image.asset(
'resources/刪除圖片.png',
width: 16.0,
height: 16.0,
),
onTap: () {
print('點擊了刪除');
if (imageModel != null) {
deleteFun(this);
}
},
),
),
],
));
}
}
這裏沒有太多難點 ,主要是佈局和事件傳遞
第三步:實現一個ImagePicker,裏面要有存儲圖片的數據源,初始化的時候先添加一個圖片狀態的item,然後每次選擇圖片或拍照之後再添加有圖狀態的item,這裏需要注意
1.添加到最大count時,移除無圖狀態的item
2.刪除的時候再添加一個無圖狀態的item
class _UcarImagePickerState extends State<UcarImagePicker> {
List _images = []; //保存添加的圖片
int currentIndex = 0;
bool isDelete = false;
@override
void initState() {
// TODO: implement initState
super.initState();
_images.add(UploadImageItem(
callBack: (int i) {
if (i == 0) {
print('打開相機');
_getImage(PickImageType.camera);
} else {
print('打開相冊');
_getImage(PickImageType.gallery);
}
},
));
}
_getImage(PickImageType type) async {
var image = await ImagePicker.pickImage(
source: type == PickImageType.gallery
? ImageSource.gallery
: ImageSource.camera);
UploadImageItem();
setState(() {
print('add image at $currentIndex');
_images.insert(
_images.length - 1,
UploadImageItem(
imageModel: UploadImageModel(image, currentIndex),
deleteFun: (UploadImageItem item) {
print('remove image at ${item.imageModel.imageIndex}');
bool result = _images.remove(item);
print('left is ${_images.length}');
if (_images.length == widget.maxCount -1 && isDelete == false) {
isDelete = true;
_images.add(UploadImageItem(
callBack: (int i) {
if (i == 0) {
print('打開相機');
_getImage(PickImageType.camera);
} else {
print('打開相冊');
_getImage(PickImageType.gallery);
}
},
));
}
print('remove result is $result');
setState(() {});
},
));
currentIndex++;
if (_images.length == widget.maxCount + 1) {
_images.removeLast();
isDelete = false;
}
});
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.only(top: 14, left: 20, bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
widget.title,
style: TextStyle(
fontSize: 15.0,
color: Color(0xFF666666),
),
),
SizedBox(
height: 22,
),
Wrap(
alignment: WrapAlignment.start,
runSpacing: 10,
spacing: 10,
children: List.generate(_images.length, (i) {
return _images[i];
}),
)
],
),
);
}
}
基本到這裏就把上述需求完成了,其中選擇相機/相冊的彈框,再之前的文章裏面有介紹,這裏就不再贅述了。