flutter 實現一個圖片選擇控件

在最近的開發中,需要做一個選擇圖片(包括拍照和相冊選擇)然後上傳的功能,我們的項目是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];
            }),
          )
        ],
      ),
    );
  }
}

基本到這裏就把上述需求完成了,其中選擇相機/相冊的彈框,再之前的文章裏面有介紹,這裏就不再贅述了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章