【flutter封装图片/视频选择控件】

引入库 wechat_assets_picker: ^6.0.5video_player: ^2.5.1 # 视频播放 flutter_screenutil: ^5.7.0

dart 复制代码
import 'dart:async';
import 'dart:io';
import 'package:generated/l10n.dart';
import 'package:jade/configs/PathConfig.dart';
import 'package:jade/customWidget/addImageVideoBtn.dart';
import 'package:jade/utils/DialogUtils.dart';
import 'package:jade/utils/JadeColors.dart';
import 'package:jade/utils/Utils.dart';
import 'package:util/easy_loading_util.dart';
import 'package:util/permission_util.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:video_player/video_player.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';

class SelectFileData {
  File file;
  int type; // 1:image  2:video 3:audio  default:other

  SelectFileData({this.file, this.type});
}

/*
* 图片/视频选择
* 只能选择一条视频,选择多条视频未完善所以存在选多条视频时每条视频都相同的bug
* */
class SelectImageVideo extends StatefulWidget {
  String title;
  String desc;
  String postscript;
  int maxLength; //最大选择数量
  RequestType requestType;
  bool discrete; //是否分离单独选择(只能选图片或视频)
  bool showExample; //是否显示查看示例按钮
  Color bgColor; //按钮背景颜色
  Function selectBack;

  SelectImageVideo(
      {this.title, this.desc,this.postscript, this.maxLength, this.requestType, this.discrete = false,this.showExample = false, this.bgColor,this.selectBack});

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _SelectImageVideo();
  }
}

class _SelectImageVideo extends State<SelectImageVideo> {
  List<SelectFileData> _selectFileList = [];
  List<File> _backFileList = [];

  VideoPlayerController _videoPlayerController;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text.rich(TextSpan(
          children: [
            TextSpan(
              text: widget.title,
              style: TextStyle(color: JadeColors.grey_2, fontSize: 30.sp, fontWeight: FontWeight.w600)
            ),
            if(widget.postscript != null)
            TextSpan(
                text: widget.postscript,
                style: TextStyle(color: JadeColors.grey, fontSize: 24.sp, fontWeight: FontWeight.w600)
            ),
          ]
        )),
        if (widget.desc != null)
          Container(
              margin: EdgeInsets.only(top: 10.w),
              child: Text(widget.desc, style: TextStyle(color: JadeColors.grey, fontSize: 24.sp))),
        SizedBox(height: 30.w),
        Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            if (widget.showExample)
              GestureDetector(
                child: Container(
                    margin: EdgeInsets.only(right: 20.w),
                    child: Stack(
                      alignment: Alignment.center,
                      children: [
                        ClipRRect(
                          borderRadius: BorderRadius.circular(8),
                          child: Image.asset(PathConfig.imageExperienceExample,
                              fit: BoxFit.fill, width: 220.w, height: 220.w),
                        ),
                        Container(
                          width: 220.w,
                          height: 220.w,
                          decoration: BoxDecoration(color: Colors.black45, borderRadius: BorderRadius.circular(8)),
                        ),
                        Text('点击查看示例', style: TextStyle(color: Colors.white, fontSize: 28.sp))
                      ],
                    )),
                onTap: () {
                  Utils().hideKeyboard(context);
                  DialogUtils().experienceStationRealisticImagesDialog(
                      title: '实景图示例',
                      desc: '需拍摄清晰格口照片,并参照线上体验秀格口序号,在图片对应位置标注对应序号。',
                      imageUrl: PathConfig.httpExperienceRealisticImages);
                },
              ),
            Expanded(
                child: SizedBox(
              height: 220.w,
              child: ListView.separated(
                  scrollDirection: Axis.horizontal,
                  itemBuilder: (context, index) {
                    if (_selectFileList.length < widget.maxLength && index == _selectFileList.length) {
                      return GestureDetector(
                          child: addImageVideoBtn(widget.requestType == RequestType.video
                              ? '添加视频'
                              : widget.requestType == RequestType.image
                                  ? '添加图片'
                                  : widget.requestType == RequestType.common
                                      ? '添加图片/视频'
                                      : '添加图片/视频/音频',
                              widget.bgColor ?? JadeColors.grey_5),
                          onTap: () async {
                            Utils().hideKeyboard(context);
                            bool _isAuth = await PermissionUtil.isAuthStorage();
                            if (!_isAuth) {
                              WidgetsBinding.instance.addPostFrameCallback((_) {
                                DialogUtils()
                                    .showGeneralDialogFunction(context, '存储权限', '用于上传照片、视频等场景', notClose: true);
                                Future.delayed(Duration(seconds: 5), () {
                                  Navigator.of(context).pop();
                                });
                              });
                            }
                            if(widget.discrete){
                              _openImageOrVideoSelect(index);
                            }else{
                              _callSelectImageVideo(index);
                            }
                            _backFileCall();
                          });
                    }
                    return Stack(
                      alignment: Alignment.topRight,
                      children: [
                        Container(
                            height: 220.w,
                            width: 220.w,
                            decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
                            child: ClipRRect(
                                //是ClipRRect,不是ClipRect
                                borderRadius: BorderRadius.circular(8),
                                child: _selectFileList[index].type == 2
                                    ? Stack(
                                        alignment: Alignment.center,
                                        children: [
                                          VideoPlayer(_videoPlayerController),
                                          Container(
                                              width: 60.w,
                                              height: 60.w,
                                              child: Image.asset(
                                                'images/video/icon_pause.png',
                                                fit: BoxFit.fill,
                                              ))
                                        ],
                                      )
                                    : Image.file(_selectFileList[index].file,
                                        width: 220.w,
                                        height: 220.w,
                                        cacheWidth: 100,
                                        cacheHeight: 100,
                                        fit: BoxFit.fill,
                                        frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
                                        if (wasSynchronouslyLoaded) {
                                          return child;
                                        }
                                        return AnimatedOpacity(
                                          child: child,
                                          opacity: frame == null ? 0 : 1,
                                          duration: const Duration(seconds: 1),
                                          curve: Curves.easeOut,
                                        );
                                      }))),
                        GestureDetector(
                            child: Container(
                                padding: EdgeInsets.all(5),
                                child: Image.asset(PathConfig.iconDeleteImageWhite, width: 34.w, height: 34.w)),
                            onTap: () {
                              if(_selectFileList[index].type == 2){
                                _videoPlayerController = null;
                              }
                              _selectFileList.removeAt(index);

                              _backFileCall();
                            })
                      ],
                    );
                  },
                  shrinkWrap: true,
                  separatorBuilder: (context, index) => Container(width: 20.w),
                  itemCount:
                      _selectFileList.length < widget.maxLength ? _selectFileList.length + 1 : _selectFileList.length),
            ))
          ],
        )
      ],
    );
  }

  //判断是否已经选择了视频
  bool _selectedVideo(){
    for (var selectFile in _selectFileList) {
      if(selectFile.type == 2){
        return true;
      }
    }
    return false;
  }

  //选择弹窗
  _openImageOrVideoSelect(int index) async {
    int value = await showCupertinoModalPopup<int>(
      builder: (BuildContext context) => CupertinoActionSheet(
        actions: <Widget>[
          CupertinoActionSheetAction(
            child: Text(S.current.p12),
            onPressed: (){
              widget.requestType = RequestType.image;
              _callSelectImageVideo(index);
              Navigator.pop(context, 1);
            },
          ),
          CupertinoActionSheetAction(
            child: Text(S.current.p13),
            onPressed: (){
              if(_selectedVideo()){
                esLoadingToast('已选择一条视频');
                Navigator.pop(context, 2);
                return;
              }
              widget.requestType = RequestType.video;
              _callSelectImageVideo(index);
              Navigator.pop(context, 2);
            },
          ),
        ],
        cancelButton: CupertinoActionSheetAction(
          child: Text(S.current.quxiao),
          onPressed: () => Navigator.pop(context, 3),
        ), // 取消按钮
      ),
      context: context,
    );
  }

  //调用图片选择器
  _callSelectImageVideo(int index) async {
    List<SelectFileData> _resultFileList = await selectImages(requestType: widget.requestType);
    if (_resultFileList.isNotEmpty) {
      setState(() {
        _selectFileList.addAll(_resultFileList);
      });
      if (_selectFileList[index].type == 2) {
        VideoPlayerController _dvideoPlayerController = VideoPlayerController.file(_selectFileList[index].file);
        _dvideoPlayerController.initialize().then((_) {
            Duration duration = _videoPlayerController.value.duration;
            int videoTime = (duration.inMinutes * 60) + duration.inSeconds;
            if (videoTime > 60) {
              esLoadingToast('发布视频长度不能大于1分钟');
              _dvideoPlayerController = null;
              _videoPlayerController = null;
              setState(() {
                _selectFileList.removeAt(index);
              });
            }
          });
        _videoPlayerController = _dvideoPlayerController;
      }
    }
  }

  _backFileCall() {
    _backFileList.clear();
    if (widget.selectBack != null) {
      _selectFileList.forEach((element) {
        _backFileList.add(element.file);
      });
      widget.selectBack(_backFileList);
    }
    setState(() {});
  }

  //图片选择器
  Future<List<SelectFileData>> selectImages({RequestType requestType}) async {
    Completer<List<SelectFileData>> _completer = Completer<List<SelectFileData>>();
    List<SelectFileData> _imageFiles = [];
    try {
      List<AssetEntity> images = await AssetPicker.pickAssets(context,
          maxAssets: requestType == RequestType.video ? 1 : widget.maxLength - _selectFileList.length, requestType: requestType ?? RequestType.image);
      if (images != null && images.length > 0) {
        for (int i = 0; i < images.length; i++) {
          var _type = images[i].typeInt;
          File _file = await images[i].file;

          SelectFileData _selectFileData = SelectFileData(file: _file, type: _type);

          _imageFiles.add(_selectFileData);
        }
        _completer.complete(_imageFiles);
      } else {
        _completer.complete([]);
      }
    } on Exception catch (e) {
      print(e);
    }
    return _completer.future;
  }
}

添加按钮

dart 复制代码
import 'package:jade/configs/PathConfig.dart';
import 'package:jade/utils/JadeColors.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

Widget addImageVideoBtn(String btnTitle,Color bgColor){
  return Container(
    width: 220.w,
    height: 220.w,
    padding: EdgeInsets.symmetric(horizontal: 10),
    decoration: BoxDecoration(
        color: bgColor,
        borderRadius: BorderRadius.circular(10)
    ),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Image.asset(PathConfig.iconAddGrey,width: 22.w,height: 22.w),
        Flexible(child: Text(btnTitle,style: TextStyle(fontSize: 24.sp,color: JadeColors.grey_18),maxLines: 2,textAlign: TextAlign.center))
      ]
    ),
  );
}

调用

dart 复制代码
 //上传反馈图片模块
  _feedbackSelectImage(){
    return Container(
      margin: EdgeInsets.only(top: 40.w),
      child: SelectImageVideo(
          title: '反馈',
          postscript: '(可上传5张图和60s视频)',
          maxLength: 6,
          requestType: RequestType.common,
          discrete: true,
          bgColor: Colors.white,
          selectBack: (selectedFiles){
            _selectFeedbackImageFiles = selectedFiles;
          }
      )
    );
  }
相关推荐
aPurpleBerry11 分钟前
JS常用数组方法 reduce filter find forEach
javascript
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x1 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
半开半落2 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
理想不理想v2 小时前
vue经典前端面试题
前端·javascript·vue.js
小阮的学习笔记2 小时前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜2 小时前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=2 小时前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css