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

news2024/11/24 17:40:02

在这里插入图片描述在这里插入图片描述
引入库 wechat_assets_picker: ^6.0.5video_player: ^2.5.1 # 视频播放 flutter_screenutil: ^5.7.0

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});

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

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

  VideoPlayerController _videoPlayerController;

  
  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;
  }
}

添加按钮

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))
      ]
    ),
  );
}

调用

 //上传反馈图片模块
  _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;
          }
      )
    );
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1565974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gateway应用(1)

1 简介 简单理解---业务服务的统一入口&#xff0c;方便实现&#xff0c;服务路由&#xff0c;安全&#xff0c;限流&#xff0c;过滤&#xff0c;黑白名单&#xff0c;证书加密解密&#xff0c;服务降级/熔断&#xff0c;灰度&#xff0c;等等 2 介绍 Predicate&#xff08…

Raven:一款功能强大的CICD安全分析工具

关于Raven Raven是一款功能强大的CI/CD安全分析工具&#xff0c;该工具旨在帮助广大研究人员对GitHub Actions CI工作流执行大规模安全扫描&#xff0c;并将发现的数据解析并存储到Neo4j数据库中。 Raven&#xff0c;全称为Risk Analysis and Vulnerability Enumeration for C…

Linux操作系统基础入门​

1、操作系统简介​ a. DOS 和 Windows NT​ DOS是一个与Unix完全不相干的操作系统&#xff0c;这一点可以从DOS使用反斜杠来表示文件目录分隔符上 看出来。随着IBM个人PC的流行&#xff0c;微软DOS操作系统在个人电脑上流行起来。Windows 95, Windows 98, 和 Windows ME底层都…

Databend 集成 PRQL:现代数据处理的一小步

PRQL&#xff0c;读作 “Prequel”&#xff0c;是一种与 SQL 并肩的查询语言&#xff0c;它的独到之处在于采用了管道式的语法&#xff0c;在查询关系数据库时显得更加直观和高效。 Databend 拥抱 PRQL 在 v1.2.380-nightly 版本中&#xff0c;得益于社区贡献者 ncuwaln 提交…

《PDVC》论文笔记

PS&#xff1a;模型代码解释清明后出 原文链接&#xff1a; [2108.07781v1] End-to-End Dense Video Captioning with Parallel Decoding (arxiv.org) 原文笔记&#xff1a; What&#xff1a; End-to-End Dense Video Captioning with Parallel Decoding 并行解码的端到端…

法律行业案例法模型出现,OPenAI公布与法律AI公司Harvey合作案例

Harvey与OpenAl合作&#xff0c;为法律专业人士构建了一个定制训练的案例法模型。该模型是具有复杂推理广泛领域知识以及超越单一模型调用能力的任务的AI系统&#xff0c;如起草法律文件、回答复杂诉讼场景问题以及识别数百份合同之间的重大差异。 Harvey公司由具有反垄断和证…

uniapp,文字超出几行显示省略号...,展开显示更多

效果图&#xff1a; 代码&#xff1a; <template><view class"text-container"><text class"text-content" click"showDetail">{{ text }}</text><text v-if"showMore" class"view-detail" cli…

ModusToolbox 实战入门- XMC GPIO应用篇

导读 ModusToolbox™ 软体&#xff1a;MCU 开发的利器 ModusToolbox™ 软体是一组支援 MCU 周边配置和应用的工具和发展。这些工具使您能够将我们的 MCU 整合到您现有的开发方法中。 ModusToolbox™ 软体的优点 提供完整的 MCU 周边配置和应用工具可整合到现有的开发方法中…

C++——异常机制

目录 一&#xff0c;背景 1.1 C语言处理错误的方式 1.2 C异常概念 二&#xff0c;异常的使用 2.1 异常的简单使用 2.2 异常的匹配原则 2.3 异常抛对象 2.4 异常的重新抛出 2.5 异常安全 三&#xff0c;自定义异常体系 四&#xff0c;异常优缺点 4.1 优点 4.2 缺点 …

NOIP2014提高组D1T2:联合权值

题目链接 NOIP2014提高组D1T2&#xff1a;联合权值 题目描述 无向连通图 G G G 有 n n n 个点&#xff0c; n − 1 n-1 n−1 条边。点从 1 1 1 到 n n n 依次编号,编号为 i i i 的点的权值为 W i W_i Wi​&#xff0c;每条边的长度均为 1 1 1。图上两点 ( u , v ) (…

环保用电监测系统诞生与作用

随着全球能源危机的加剧和环境保护意识的提高&#xff0c;环保用电监测系统应运而生。这一系统以其独特的监测能力、数据分析和节能减排功能&#xff0c;在提高用电效率和促进环境可持续发展方面发挥着重要作用。本文将从环保用电监测系统的诞生背景、主要功能、作用以及在实际…

基于Springboot + MySQL + Vue 大学新生宿舍管理系统 (含源码)

目录 &#x1f4da; 前言 &#x1f4d1;摘要 &#x1f4d1;操作流程 &#x1f4da; 系统架构设计 &#x1f4da; 数据库设计 &#x1f4ac; 管理员信息属性 &#x1f4ac; 学生信息实体属性 &#x1f4ac; 宿舍安排信息实体属性 &#x1f4ac; 卫生检查信息实体属性 &…

leet hot 100-10 和为 K 的子数组

和为 K 的子数组 原题链接思路代码 原题链接 leet hot 100-10 560. 和为 K 的子数组 思路 看到连续非空数组 想到前缀和数组 首先记录前缀和 然后从前往后运算 计算当前位置的前缀和的大小 减少k个 那么这个数字在前缀和的数组中有多少 时间复杂度O(n) 空间复杂度(n) 代…

python基础——模块【模块的介绍,模块的导入,自定义模块,*和__all__,__name__和__main__】

&#x1f4dd;前言&#xff1a; 这篇文章主要讲解一下python基础中的关于模块的导入&#xff1a; 1&#xff0c;模块的介绍 2&#xff0c;模块的导入方式 3&#xff0c;自定义模块 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C语言入门基…

招聘信息分享(第一期)

今天给大家带来——测绘、地信、遥感领域的事业单位招聘信息&#xff01;这也是我自己在关注的&#xff0c;自己应聘单位大多时间已经截至&#xff0c;后期会陆续分享&#xff0c;先分享近期招聘的事业单位 文章目录 1、宁夏大学2024年人才招聘2、甘肃有色冶金职业技术学院3、…

【现代企业管理】企业组织结构和组织文化的理论与实践——以华为为例

一、前言 管理是科学和艺术的统一体&#xff0c;它是企业成长的保证。企业管理中&#xff0c;管理者面对的往往不是一个完整的系统&#xff0c;而是各种不具有整体规律性的零碎信息的总和&#xff0c;因此进行信息的整合和研究是管理的重点和关键。 组织管理作为管理的四大职…

【QingHub】QingHub Studio企业级应用作业编排

简介 QingHub作业编排中心是一个通过插件化方式&#xff0c;提供数据从采集&#xff0c;转化&#xff0c;计算&#xff0c;存储为一体的全流程数据处理方案&#xff0c;他一方面为前端应用提供数据源&#xff0c;同时也为前端应用与数据源头的通信搭建起桥梁&#xff0c;实现数…

链表之单链表

上一篇博客我们学习了线性表中的顺序表&#xff0c;这一篇博客让我们继续往下了解线性表的链表&#xff0c;链表分为好几种结构&#xff0c;活不多说&#xff0c;让我们开始学习吧&#xff01; 目录 1.链表 2.链表的结构 3.单链表的实现 1.链表 1.概念&#xff1a;它是一种物…

快速跨国传输怎么实现?

在当今全球化的商业舞台上&#xff0c;迅速且安全地跨国界传输大型文件已经成为企业运营的一个核心环节。但是&#xff0c;这一过程往往面临速度缓慢和安全隐患的问题&#xff0c;这些问题严重地影响了企业的工作效率和数据的安全性。小编将会深入探讨企业在进行跨国大文件传输…

揭秘!自定义三维模型如何在RflySim中实现仿真(三)

一.技术背景 揭秘&#xff01;自定义三维模型如何在RflySim中实现仿真&#xff08;一&#xff09; 揭秘&#xff01;自定义三维模型如何在RflySim中实现仿真&#xff08;二&#xff09; 上两篇文章我们学习了自定义三维模型如何在RflySim中实现仿真和三维场景导入RflySim的实…