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

在这里插入图片描述在这里插入图片描述
引入库 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.mfbz.cn/a/513586.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

The Sandbox 的伙伴们| K-verse 3: Eternal Fandom 的合作伙伴介绍

准备好参加韩国合作伙伴最盛大的聚会吧&#xff0c;就在The Sandbox&#xff01;这是一个前所未有的与你最喜爱的品牌建立联系的绝佳机会。 Otherworld Web 3.0 社交 "Otherworld "的首个数字空间 国内领先网络动漫和娱乐 IP 汇聚并扩展的元宇宙 Otherworld 正在为 …

高级DBA带你解决Mysql主从集群主库产生过多binlog文件引起生产服务器硬盘爆满处理方法实战全网唯一

高级DBA带你解决Mysql主从集群产生过多binlog文件引起生产服务器硬盘爆满处理方法实战全网唯一 一、事故描述 生产环境数据库服务器突然硬盘爆满报警&#xff0c;业务停止&#xff0c;监控短信过来了&#xff0c;一看硬盘满了&#xff0c;再看数据库文件路径一大堆binlog文件…

Redis 主从复制,哨兵模式,集群

目录 主从复制 主从复制 作用 缺陷 主从复制流程 实现Redis主从复制 哨兵模式 主从复制切换的缺点 哨兵的核心功能 哨兵模式原理 哨兵模式的作用 哨兵结构组成 故障转移机制 主节点的选举 实现哨兵模式 集群(Cluster) redis群集有三种模式&#xff0c;主从复制…

Leetcode-894-所有可能的真二叉树-c++

题目详见https://leetcode.cn/problems/all-possible-full-binary-trees/ 主搞动态规划&#xff0c;因为这玩意儿我还不是很懂 关于节点个数为奇数偶数的证明请见官方题解方法一中的如下内容&#xff1a; 这里DP的一个主要思想是&#xff1a;对于任何一个满二叉树&#xff…

算法学习——LeetCode力扣动态规划篇9(1035. 不相交的线、53. 最大子数组和、392. 判断子序列、115. 不同的子序列)

算法学习——LeetCode力扣动态规划篇9 1035. 不相交的线 1035. 不相交的线 - 力扣&#xff08;LeetCode&#xff09; 描述 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#x…

网站可扩展架构设计——中台

从公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、中台简介 1.传统项目架构的痛点 (1)重复造轮子 各项目相对独立&#xff0c;许多项目在重复造轮子&#xff0c;让项目本身越来越臃肿&#xf…

外卖配送时间预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 项目背景 外卖服务的兴起: 随着互联网技术和移动应用的发展&#xff0c;外卖成为一种日益普及的餐饮服务方式。顾客通过餐厅、杂货店的网站或移…

OpenHarmony Neptune开发板-MQTT连接华为IoT平台

本示例将演示如何在Neptune开发板上使用MQTT协议连接华为IoT平台,使用的是ATH20温湿度传感器模块与Neptune开发板 本示例实现AHT20温湿度数据上报华为IoT平台,IoT平台下发命令控制LED灯的开关 使用W800 SDK功能包中libemqtt来实现连接华为IoT平台 程序设计 初始化 一、MQT…

Stable Diffusion 模型下载:CyberRealistic(真实)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;订阅后可阅读专栏内所有文章&#xff0c;专栏总目录•点这里 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 这是经过严格测试过程的结果&#xff0c;该过程混合了各种模型…

存储故障处理流程演变

存储作为存放金融企业数据中心各类生产数据的重要载体&#xff0c;其日常的安全平稳运行至关重要。特别是应对若干存储的大量告警&#xff0c;如何从大量告警中提取关键告警消息并及时处理异常&#xff0c;可谓对存储平台的稳定运行起到保驾护航的作用。 存储告警处理作为常规…

如何监控特权帐户,保护敏感数据

IT基础设施的增长导致员工可以访问的凭据和资源数量急剧增加。每个组织都存储关键信息&#xff0c;这些信息构成了做出关键业务决策的基石。与特权用户共享这些数据可以授予他们访问普通员工没有的凭据的权限。如果特权帐户凭证落入不法分子之手&#xff0c;它们可能被滥用&…

2024最新AI创作系统ChatGPT源码+Ai绘画网站源码,GPTs应用、AI换脸、插件系统、GPT文档分析、GPT语音对话一站式解决方案

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

Ai音乐大师演示(支持H5、小程序)独立部署源码

Ai音乐大师演示&#xff08;支持H5、小程序&#xff09;独立部署源码

Python网络爬虫(三):Selenium--以携程酒店为例

1 Selenium简介 Selenium是一个用于网站应用程序自动化的工具&#xff0c;它可以直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。它相当于一个机器人&#xff0c;可以模拟人类在浏览器上的一些行为&#xff0c;比如输入文本、点击、回车等。Selenium支持多种浏览器&…

Linux结构目录详解

Linux 在Linux中&#xff0c;系统默认的用户是root&#xff0c;其实和 windows 的 administrator 类似&#xff0c;root 用户可以操作操作系统的任何文件和设备&#xff0c;所以在生产环境就不要乱用root了&#xff0c;权利越大&#xff0c;责任越大。 学习Linux&#xff0c;…

C++ 项目:使用 GSL 数学运算库 C++ 调用Python

文章目录 Part.I IntroductionChap.I CMakeListsChap.II ExportLibGSL.hChap.III test_python.cpp Part.II GSL 使用方法Part.III C 调用 Python 使用方法相关博客 Part.I Introduction 本文是一个项目的使用教程&#xff0c;此项目是一个使用 GSL 的小项目&#xff0c;还有 C…

Solana 线下活动回顾|多方创新实践,引领 Solana“文艺复兴”新浪潮

Solana 作为在过去一年里实现突破式飞跃的头部公链&#xff0c;究竟是如何与 Web3 行业共振&#xff0c;带来全新的技术发展与生态亮点的呢&#xff1f;在 3 月 24 日刚结束的「TinTin Destination Moon」活动现场&#xff0c;来自 Solana 生态的的专家大咖和 Web3 行业的资深人…

基于lora技术微调Gemma(2B)代码实践

一、前置条件 获得模型访问权&#xff0c;选择Colab运行时&#xff0c;配置训练环境。 先在Kaggle上注册&#xff0c;然后获得Gemma 2B 的访问权&#xff1b; 然后在Google colab 配置环境&#xff0c;主要是GPU的选择&#xff0c;免费的是T4&#xff0c;建议采用付费的A100…

【Linux】详解动静态库的制作和使用动静态库在系统中的配置步骤

一、库的作用 1、提高开发效率&#xff0c;让开发者所有的函数实现不用从零开始。 2、隐藏源代码。 库其实就是所有的.o文件用特定的方式进行打包形成一个文件&#xff0c;各个.o文件包含了源代码中的机器语言指令。 二、动态库和静态库的制作和使用 2.1、静态库的制作和使用…

DTFT及其反变换的直观理解

对于离散时间傅里叶变换(DTFT)及其反变换的讲解&#xff0c;教材里通常会先给出DTFT正变换的公式&#xff0c;再举个DTFT的简单变换例子&#xff0c;推导一下DTFT的性质&#xff0c;然后给出DTFT反变换的公式&#xff0c;再证明一下正变换和反变化的对应关系。总的来说就是&…
最新文章