flutter 实现一个图片选择控件

在最近的开发中,需要做一个选择图片(包括拍照和相册选择)然后上传的功能,我们的项目是iOS原生和flutter混编的,首先用flutter实现这个页面,选择了第三方插件image_picker,下面先看一下效果图

flutter 实现一个图片选择控件_第1张图片

下面我们开始一步一步实现这个页面的逻辑,核心是在实现一个可复用的图片选择控件,支持设置最大选择图片数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 /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: [
            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: [
                            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 {
  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: [
          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];
            }),
          )
        ],
      ),
    );
  }
}

基本到这里就把上述需求完成了,其中选择相机/相册的弹框,再之前的文章里面有介绍,这里就不再赘述了。

更新了这个demo,加入了camera和cached_network_image的使用,主要介绍一下camera的使用

- 添加所需依赖

dependencies:
  flutter:
    sdk: flutter
  camera:
  path_provider:
  path:

- 获取可用相机列表

// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();

// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first; 

- 创建并初始化 CameraController

// A screen that takes in a list of cameras and the Directory to store images.
class TakePictureScreen extends StatefulWidget {
  final CameraDescription camera;

  const TakePictureScreen({
    Key key,
    @required this.camera,
  }) : super(key: key);

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State {
  // Add two variables to the state class to store the CameraController and
  // the Future.
  CameraController _controller;
  Future _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // To display the current output from the camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Fill this out in the next steps.
  }
}

- 使用 CameraController 拍摄一张图片

FloatingActionButton(
  child: Icon(Icons.camera_alt),
  // Provide an onPressed callback.
  onPressed: () async {
    // Take the Picture in a try / catch block. If anything goes wrong,
    // catch the error.
    try {
      // Ensure that the camera is initialized.
      await _initializeControllerFuture;

      // Construct the path where the image should be saved using the path
      // package.
      final path = join(
        // Store the picture in the temp directory.
        // Find the temp directory using the `path_provider` plugin.
        (await getTemporaryDirectory()).path,
        '${DateTime.now()}.png',
      );

      // Attempt to take a picture and log where it's been saved.
      await _controller.takePicture(path);
    } catch (e) {
      // If an error occurs, log the error to the console.
      print(e);
    }
  },
)

- 使用 CameraPreview 展示相机的帧流

// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
FutureBuilder(
  future: _initializeControllerFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      // If the Future is complete, display the preview.
      return CameraPreview(_controller);
    } else {
      // Otherwise, display a loading indicator.
      return Center(child: CircularProgressIndicator());
    }
  },
)

- 使用 Image 组件展示图片

class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Display the Picture')),
      // The image is stored as a file on the device. Use the `Image.file`
      // constructor with the given path to display the image.
      body: Image.file(File(imagePath)),
    );
  }
}

 

demo地址:https://github.com/AnleSu/image_picker_demo

你可能感兴趣的:(flutter开发)