Flutter开发模仿百度云盘创建文件夹功能Draggable和DragTarget的混合使用

news2025/1/16 0:53:13

使用LongPressDraggableDragTarget写了个类似于百度云盘管理文件和文件夹的功能(为了避免和列表的滑动手势冲突,所以采用LongPressDraggable而不是Draggable):

1、拖拽文件到文件夹中
2、拖拽两个文件可以合并成一个新的文件夹

效果如下:

实现效果

1、文件夹可以拖拽到另外一个文件夹中去
2、文件夹不可以拖拽到设备中去
3、设备可以拖拽到文件夹中去
4、两个设备可以合并成一个新的文件夹

使用到的三方 get: ^4.6.6

gif.gif

代码展示(代码注释都写的比较清楚,如果有不懂的可以在下方留言)

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class DraggableListView extends StatefulWidget {
  const DraggableListView({super.key});

  @override
  State<DraggableListView> createState() => _DraggableListViewState();
}

class _DraggableListViewState extends State<DraggableListView> {
  final ScrollController _scrollController = ScrollController();
  final TextEditingController _nameController = TextEditingController();
  final List<Map<String, dynamic>> _gatherList = [
    {'label': '顺义区'},
    {'label': '朝阳区'},
    {'label': '通州区'},
    {'label': '密云区'},
    {'label': '海淀区'},
  ];
  final List<Map<String, dynamic>> _deviceList = [
    {'label': '设备---1'},
    {'label': '设备---2'},
    {'label': '设备---3'},
    {'label': '设备---4'},
    {'label': '设备---5'},
    {'label': '设备---6'},
    {'label': '设备---7'},
    {'label': '设备---8'},
    {'label': '设备---9'},
    {'label': '设备---10'},
    {'label': '设备---11'},
  ];

  ///当前拖拽的cell的index
  int dragIndex = 0;

  ///判断拖拽的是文件夹还是设备
  bool isDragFile = false;
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
    _nameController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('DraggableListView'),
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    Color bgColor = Colors.black38;
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            controller: _scrollController,
            itemCount: _deviceList.length + _gatherList.length,
            itemExtent: cellHeight,
            itemBuilder: (context, index) {
              ///文件夹列表
              if (index < _gatherList.length) {
                return Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 10.0,
                    vertical: 5.0,
                  ),
                  child: LongPressDraggable(
                    data: index,
                    //拖拽的文件夹内容展示
                    feedback: _buildFeedbackContainer(
                      index: index,
                      isFile: true,
                    ),
                    onDragStarted: () {
                      dragIndex = index;
                      isDragFile = true;
                    },
                    //被拖拽的文件夹cell在列表中的展示
                    childWhenDragging: _buildContainerWhenDragging(),
                    onDragUpdate: (details) {
                      // 拖拽让列表上下滚动
                      _scrollListView(details);
                    },
                    child: DragTarget<int>(
                      onAccept: (int data) {
                        if (!isDragFile) {
                          ///
                          Get.snackbar("提示",
                              "${_deviceList[data]}被移动到${_gatherList[index]}中去了");

                          ///如果拖拽的是设备放到文件夹上,就移除设备
                          _deviceList.removeAt(data);
                        } else {
                          ///如果拖拽的是文件夹,当拖拽的文件夹和被拖拽的文件夹不是一个的时候,合并文件夹
                          if (dragIndex != index) {
                            ///
                            Get.snackbar("提示",
                                "${_gatherList[data]}被移动到${_gatherList[index]}中去了");

                            ///如果拖拽的是文件夹放到文件夹上,就移除文件夹
                            _gatherList.removeAt(data);
                          }
                        }
                        setState(() {});
                      },
                      onWillAccept: (data) {
                        if (isDragFile) {
                          ///当拖拽的是某个文件夹的时候,如果拖拽的文件夹放到被拖拽的文件夹上面的时候,不改变原来文件夹的状态(背景色)
                          if (dragIndex != index) {
                            bgColor = Colors.red;
                          }
                        } else {
                          bgColor = Colors.red;
                        }
                        return data != null;
                      },
                      onLeave: (data) {
                        bgColor = bgColor;
                        setState(() {});
                      },
                      builder: (context, candidateData, rejectedData) {
                        ///文件夹的cell展示
                        return Container(
                          alignment: Alignment.center,
                          decoration: BoxDecoration(
                            color: bgColor,
                            borderRadius: const BorderRadius.all(
                              Radius.circular(18.0),
                            ),
                          ),
                          child: _buildGatherCell(index),
                        );
                      },
                    ),
                  ),
                );
              }

              ///设备列表
              return Container(
                margin: const EdgeInsets.symmetric(
                  horizontal: 10.0,
                  vertical: 5.0,
                ),
                child: LongPressDraggable(
                  data: index - _gatherList.length,
                  //拖拽的设备内容展示
                  feedback: _buildFeedbackContainer(
                    index: index,
                    isFile: false,
                  ),
                  //被拖拽的设备cell在列表中的展示
                  childWhenDragging: _buildContainerWhenDragging(),
                  onDragStarted: () {
                    isDragFile = false;
                    dragIndex = index - _gatherList.length;
                  },
                  onDragUpdate: (details) {
                    // 拖拽让列表上下滚动
                    _scrollListView(details);
                  },
                  child: DragTarget<int>(
                    onAccept: (int data) {
                      ///拖拽设备放到设备上进行合并+创建新的文件夹
                      ///如果是把文件夹拖拽到设备上不做任何操作
                      if (!isDragFile) {
                        _mergeDevice(data: data, index: index);
                      }
                    },
                    onWillAccept: (data) {
                      if (!isDragFile) {
                        if (dragIndex != (index - _gatherList.length)) {
                          bgColor = Colors.red;
                        }
                      }
                      return data != null;
                    },
                    onLeave: (data) {
                      bgColor = bgColor;
                      setState(() {});
                    },
                    builder: (context, candidateData, rejectedData) {
                      return Container(
                        alignment: Alignment.center,
                        color: bgColor,
                        child: _buildDeviceCell(index),
                      );
                    },
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  ///创建文件夹的cell
  Widget _buildGatherCell(int index) {
    return Row(
      children: [
        const SizedBox(width: 50.0),
        Expanded(
          child: Align(
            alignment: Alignment.centerLeft,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  "${_gatherList[index]["label"]}",
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 16.0,
                  ),
                ),
              ],
            ),
          ),
        ),
        const Icon(
          Icons.arrow_forward_ios,
          color: Colors.white,
        ),
        const SizedBox(width: 10.0),
      ],
    );
  }

  ///创建设备的cell
  Widget _buildDeviceCell(int index) {
    return Row(
      children: [
        const SizedBox(width: 50.0),
        Expanded(
          child: Align(
            alignment: Alignment.centerLeft,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  "${_deviceList[index - _gatherList.length]["label"]}",
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 16.0,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  ///合并两个设备-创建新的文件夹
  _mergeDevice({
    required int data,
    required int index,
  }) {
    Get.defaultDialog(
      title: "新建集合",
      titlePadding: const EdgeInsets.symmetric(vertical: 16.0),
      titleStyle: const TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.w400,
        fontSize: 16.0,
      ),
      backgroundColor: const Color.fromRGBO(25, 29, 39, 1),
      barrierDismissible: false,
      content: Column(
        children: [
          Container(
            height: 44.0,
            padding: const EdgeInsets.symmetric(horizontal: 10.0),
            margin: const EdgeInsets.symmetric(horizontal: 16.0),
            decoration: BoxDecoration(
              border: Border.all(
                color: const Color.fromRGBO(43, 82, 255, 1),
              ),
              borderRadius: BorderRadius.circular(8.0),
            ),
            alignment: Alignment.center,
            child: TextField(
              controller: _nameController,
              style: const TextStyle(color: Colors.white),
              decoration: const InputDecoration(
                border: InputBorder.none,
                enabledBorder: InputBorder.none,
                hintText: "新建集合",
                hintStyle: TextStyle(
                  color: Color.fromRGBO(255, 255, 255, 0.45),
                  fontSize: 16.0,
                ),
                isCollapsed: true,
                // 输入框内容上下居中
                contentPadding: EdgeInsets.only(top: 0, bottom: 0),
              ),
            ),
          ),
          const SizedBox(height: 20.0),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Container(
                  width: 105.0,
                  height: 44.0,
                  decoration: BoxDecoration(
                    color: const Color.fromRGBO(2, 3, 6, 1),
                    borderRadius: BorderRadius.circular(8.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                      _nameController.clear();
                      setState(() {});
                      Get.back();
                    },
                    child: const Text(
                      "取消",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                        fontWeight: FontWeight.w400,
                      ),
                    ),
                  ),
                ),
                Container(
                  width: 105.0,
                  height: 44.0,
                  decoration: BoxDecoration(
                    color: const Color.fromRGBO(43, 82, 255, 1),
                    borderRadius: BorderRadius.circular(8.0),
                  ),
                  child: TextButton(
                    onPressed: () {
                      if (_nameController.text.isEmpty) {
                        Get.snackbar("提示", "请输入名称!");
                        return;
                      }
                      for (var item in _gatherList) {
                        if (item["label"] == _nameController.text) {
                          Get.snackbar("提示", "该名称已存在,请重新输入!");
                          return;
                        }
                      }
                      var array = [
                        _deviceList[data],
                        _deviceList[index - _gatherList.length]
                      ];
                      _deviceList
                          .removeWhere((element) => array.contains(element));

                      ///删除设备之后再创建文件夹
                      _gatherList.add(
                        {'label': _nameController.text},
                      );
                      var fileName = _nameController.text;
                      _nameController.clear();
                      setState(() {});
                      Get.back();
                      Get.snackbar(
                          "提示", "${array[0]}和${array[1]}已合并到文件夹${fileName}中");
                    },
                    child: const Text(
                      "确定",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 16.0,
                        fontWeight: FontWeight.w400,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }

  ///拖拽的时候上下滚动列表
  _scrollListView(DragUpdateDetails details) {
    if (details.globalPosition.dy < 200) {
      if (_scrollController.offset <= 0.0) return;
      // 执行向下滚动操作
      _scrollController.jumpTo(_scrollController.offset - 5);
    }

    if (details.globalPosition.dy > 700) {
      if (_scrollController.offset >=
          _scrollController.position.maxScrollExtent) return;
      // 执行向上滚动操作
      _scrollController.jumpTo(_scrollController.offset + 5);
    }
  }

  ///创建拖拽过程中的内容展示
  Widget _buildFeedbackContainer({
    required int index,
    required bool isFile, //是否是文件夹
  }) {
    return Container(
      alignment: Alignment.center,
      width: Get.width,
      height: cellHeight,
      decoration: BoxDecoration(
        borderRadius: const BorderRadius.all(
          Radius.circular(10.0),
        ),
        color: Colors.yellow.withOpacity(0.6),
      ),
      child: Text(
        isFile
            ? "拖拽的内容:${_gatherList[index]["label"]}"
            : "拖拽的设备:${_deviceList[index - _gatherList.length]["label"]}",
        style: const TextStyle(
          decoration: TextDecoration.none,
          fontSize: 20.0,
          color: Colors.red,
        ),
      ),
    );
  }

  ///创建占位容器
  Widget _buildContainerWhenDragging() {
    return Container(
      alignment: Alignment.center,
      color: Colors.red,
      child: const Text(
        "我是个占位容器,真实的我被拽走了",
        style: TextStyle(
          color: Colors.black,
        ),
      ),
    );
  }
}

const cellHeight = 88.0;

简书地址

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

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

相关文章

【linux】git和gdb调试工具

在linux下提交代码同步到gitee 1.创建一个新的仓库&#xff08;演示步骤&#xff09; 2.init 这两个步骤用于识别提交代码的身份&#xff0c;一个你的名字&#xff0c;一个你的邮箱 开启本地仓库 克隆本地仓库成功 我们将这个仓库拷到了111目录底下. 我们发现少了一个.gitig…

window 镜像---负载篇

前提&#xff1a;需要修改window的powershell执行脚本的策略 步骤&#xff1a;以管理员身份打开powershell&#xff0c;执行 Get-ExecutionPolicy查看当前执行策略&#xff0c;若返回值是Restricted&#xff0c;需执行Set-ExecutionPolicy RemoteSigned powershell 版本信息&am…

SpringMVC精简知识点

SpringMVC 数据格式化基本数据类型和字符串自动转换特殊数据类型和字符串自动转换 验证及国际化应用实例注意事项和使用细节注解的结合使用数据类型转换校验核心类-DatBinder取消某个属性的绑定中文乱码解决处理json和HttpMessageConverter<T>作业布置SpringMVC文件上传自…

笔记本电脑的WIFI模块,突然不显示了,网络也连接不上

问题复现&#xff1a; 早上&#xff0c;在更新完笔记本电脑的系统之后&#xff0c;连网之后&#xff0c;网络突然直接断开&#xff0c;一查看&#xff0c;WiFi模块居然不见了&#xff0c;开机重启也是如此&#xff0c;这种情况常常出现在更新系统之后&#xff0c;WiFi模块驱动就…

MySQL集群 1主1从 主从复制(原理 及配置命令)

CSDN 成就一亿技术人&#xff01; 今天分享一期 MySQL集群方案&#xff1a;主从集群 也是最常用的一种 CSDN 成就一亿技术人&#xff01; 目录 使用主从复制的原因&#xff08;优点&#xff09; 主从复制的过程&#xff08;原理&#xff09; 了解两大线程&#xff08; I/O…

7min到40s:SpringBoot 启动优化实践!

目录 背景 1 耗时问题排查 1.1 观察 SpringBoot 启动 run 方法 1.2 监控 Bean 注入耗时 2 优化方案 2.1 如何解决扫描路径过多&#xff1f; 2.2 如何解决 Bean 初始化高耗时&#xff1f; 3 新的问题 3.1 SpringBoot 自动化装配&#xff0c;让人防不胜防 3.2 使用 sta…

大带宽服务器托管的特点和考虑因素

很多公司和企业对于使用大带宽服务器的需求和存储不一样&#xff0c;为了满足不同的用户需求&#xff0c;大带宽服务器托管是个不错的选择&#xff0c;小编为您整理发布大带宽服务器托管的特点和要考虑的因素。 大带宽服务器托管是一种服务器托管服务&#xff0c;其主要特点是…

GLIP:零样本学习 + 目标检测 + 视觉语言大模型

GLIP 核心思想GLIP 对比 BLIP、BLIP-2、CLIP 主要问题: 如何构建一个能够在不同任务和领域中以零样本或少样本方式无缝迁移的预训练模型&#xff1f;统一的短语定位损失语言意识的深度融合预训练数据类型的结合语义丰富数据的扩展零样本和少样本迁移学习 效果 论文&#xff1a;…

JetPackCompose之Text使用指北

Jetpack Compose系列(6) - 文本组件 对应View体系中传统的TextView&#xff0c;Jetpack Compose中用Text组件来显示文本信息。跟其他组件一样&#xff0c;它在构造函数里就包含控制文本显示样式的一些属性&#xff0c;下面是其参数及解释&#xff1a; Composable fun Text(te…

从零开始手写mmo游戏从框架到爆炸(一)— 开发环境

一、创建项目 1、首先创建一个maven项目&#xff0c;pom文件如下&#xff1a; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0…

力扣面试150 只出现一次的数字Ⅱ 哈希 统计数位 DFA有穷自动机

Problem: 137. 只出现一次的数字 II 文章目录 思路&#x1f496; 哈希&#x1f496; 位数统计&#x1f496; DFA 状态机 思路 &#x1f468;‍&#x1f3eb; 参考 &#x1f496; 哈希 ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( n ) O(n) O(n) cl…

《动手学深度学习(PyTorch版)》笔记7.1

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

vue 下载二进制文件

文章目录 概要技术细节 概要 vue 下载后端返回的二进制文件流 技术细节 import axios from "axios"; const baseUrl process.env.VUE_APP_BASE_API; //downLoadPdf("/pdf/download?pdfName" res .pdf, res); export function downLoadPdf(str, fil…

【Linux】信号-上

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;信号的概念与产生jobs命令普通信号和实…

JAVA-File五个练习

下面习题思路大多都是&#xff1a; 1.获取路径下所有列表&#xff08;listfiles&#xff09;&#xff0c;2.遍历文件或文件夹&#xff08;增强for&#xff09;&#xff0c;3.判断是否是文件&#xff08;isFile&#xff09;并直接执行逻辑&#xff0c;4.判断当前是文件夹的情况&…

JDK和Spring的SPI机制原理分析

目录 一、JDK 二、Spring框架介绍 三、SPI机制原理 一、JDK JDK是Java Development Kit的缩写&#xff0c;是Java开发工具包的意思。它是用于开发Java应用程序和运行Java程序的软件包。JDK包含了Java编译器&#xff08;javac&#xff09;和Java虚拟机&#xff08;JVM&#…

免费的hyper-v虚机添加U盘的二种方法

windows集成了hyper-v&#xff0c;hyper-v可以安装linux&#xff0c;windows等虚机&#xff0c;基本可以满足工作&#xff0c;实验之需。但是不少人反映hyper-v不方便连接U盘&#xff0c;这样子文件传输不是很方便。 网上有方法说在虚机设置中添加磁盘&#xff0c;首先到物理机…

信创ARM架构QT应用开发环境搭建

信创ARM架构QT应用开发环境搭建 前言交叉工具链Ubuntu上安装 32 位 ARM 交叉工具链Ubuntu上安装 64 位 ARM 交叉工具链 交叉编译 QT 库下载 QT 源码交叉编译 QT 源码 Qt Creator交叉编译配置配置 Qt Creator Kits创建一个测试项目 前言 有没有碰到过这种情况&#xff1f;开发出…

ctfshow web-76

开启环境: c?><?php $anew DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString(). );} exit(0); ?> cinclude("/flagc.txt");exit(); c?><?php $anew DirectoryIterator("glob:///*"); foreach($a…

全流程机器视觉工程开发(四)PaddleDetection C++工程化应用部署到本地DLL以供软件调用

前言 我们之前跑了一个yolo的模型&#xff0c;然后我们通过PaddleDetection的库对这个模型进行了一定程度的调用&#xff0c;但是那个调用还是基于命令的调用&#xff0c;这样的库首先第一个不能部署到客户的电脑上&#xff0c;第二个用起来也非常不方便&#xff0c;那么我们可…