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

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

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

效果如下:

实现效果

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

使用到的三方 get: ^4.6.6

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

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

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

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

class _DraggableListViewState extends State {
  final ScrollController _scrollController = ScrollController();
  final TextEditingController _nameController = TextEditingController();
  final List> _gatherList = [
    {'label': '顺义区'},
    {'label': '朝阳区'},
    {'label': '通州区'},
    {'label': '密云区'},
    {'label': '海淀区'},
  ];
  final List> _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(
                      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(
                    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;

简书地址

你可能感兴趣的:(flutter,android,ios)