使用LongPressDraggable
和DragTarget
写了个类似于百度云盘管理文件和文件夹的功能(为了避免和列表的滑动手势冲突,所以采用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});@overrideState<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的indexint dragIndex = 0;///判断拖拽的是文件夹还是设备bool isDragFile = false;@overridevoid initState() {super.initState();}@overridevoid dispose() {super.dispose();_scrollController.dispose();_nameController.dispose();}@overrideWidget 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),);},),),);},),),],);}///创建文件夹的cellWidget _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),],);}///创建设备的cellWidget _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;
简书地址