使用LongPressDraggable
和DragTarget
寫了個類似于百度云盤管理文件和文件夾的功能(為了避免和列表的滑動手勢沖突,所以采用LongPressDraggable
而不是Draggable
):
1诫隅、拖拽文件到文件夾中
2麦射、拖拽兩個文件可以合并成一個新的文件夾
效果如下:
實現(xiàn)效果
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': '順義區(qū)'},
{'label': '朝陽區(qū)'},
{'label': '通州區(qū)'},
{'label': '密云區(qū)'},
{'label': '海淀區(qū)'},
];
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) {
///當拖拽的是某個文件夾的時候单默,如果拖拽的文件夾放到被拖拽的文件夾上面的時候碘举,不改變原來文件夾的狀態(tài)(背景色)
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) {
///拖拽設備放到設備上進行合并+創(chuàng)建新的文件夾
///如果是把文件夾拖拽到設備上不做任何操作
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),
);
},
),
),
);
},
),
),
],
);
}
///創(chuàng)建文件夾的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),
],
);
}
///創(chuàng)建設備的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,
),
),
],
),
),
),
],
);
}
///合并兩個設備-創(chuàng)建新的文件夾
_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));
///刪除設備之后再創(chuàng)建文件夾
_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;
// 執(zhí)行向下滾動操作
_scrollController.jumpTo(_scrollController.offset - 5);
}
if (details.globalPosition.dy > 700) {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent) return;
// 執(zhí)行向上滾動操作
_scrollController.jumpTo(_scrollController.offset + 5);
}
}
///創(chuàng)建拖拽過程中的內容展示
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,
),
),
);
}
///創(chuàng)建占位容器
Widget _buildContainerWhenDragging() {
return Container(
alignment: Alignment.center,
color: Colors.red,
child: const Text(
"我是個占位容器,真實的我被拽走了",
style: TextStyle(
color: Colors.black,
),
),
);
}
}
const cellHeight = 88.0;