flutter - Flutter 在多个列表之间拖放列表项
问题描述
我想开发,例如将一个列表项拖到另一个可能为空的列表中。如果 dragitem 放置在另一个列表的特定项目上,则拖动的项目将被添加到它拖动到的列表的索引中,并且同样的事情也将适用于同一个列表。我试过了,但在选择拖动项时我无法让它像滚动列表视图一样,并且只有 3 个可见项目。
我想这样开发:https ://i.stack.imgur.com/bdn1I.gif
任何帮助表示赞赏。
解决方案
您尝试开发的内容可以使用Draggable和DragTarget来完成,但您必须注意Draggable 和 DragTarget 应该具有相同的数据类型
我将简要解释这两个小部件,然后在本段之后我将发布完整的代码,然后我将分别解释每个部分。
Draggable 是用于使用户能够将一个小部件从一个位置拖动到另一个位置的小部件,但它只能在相同类型的 DragTarget 小部件上拖放和处理
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) =>
MaterialApp(
home: Drag(),
);
}
class Drag extends StatefulWidget {
@override
_DragState createState() => _DragState();
}
class _DragState extends State<Drag> {
List listA = ["A", "B", "C"];
List listB = ["D", "E", "F"];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.greenAccent[200],
body: SafeArea(
child: Column(
children: [
// list view separated will build a widget between 2 list items to act as a separator
Expanded(
child: ListView.separated(
itemBuilder: _buildListAItems,
separatorBuilder: _buildDragTargetsA,
itemCount: listA.length,
)),
Expanded(
child: ListView.separated(
itemBuilder: _buildListBItems,
separatorBuilder: _buildDragTargetsB,
itemCount: listB.length,
)),
],
),
),
);
}
// builds the widgets for List B items
Widget _buildListBItems(BuildContext context, int index) {
return Draggable<String>(
// the value of this draggable is set using data
data: listB[index],
// the widget to show under the users finger being dragged
feedback: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listB[index],
style: TextStyle(fontSize: 20),
),
),
),
// what to display in the child's position when being dragged
childWhenDragging: Container(
color: Colors.grey,
width: 40,
height: 40,
),
// widget in idle state
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listB[index],
style: TextStyle(fontSize: 20),
),
),
),
);
}
// builds the widgets for List A items
Widget _buildListAItems(BuildContext context, int index) {
return Draggable<String>(
data: listA[index],
feedback: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listA[index],
style: TextStyle(fontSize: 20),
),
),
),
childWhenDragging: Container(
color: Colors.grey,
width: 40,
height: 40,
),
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listA[index],
style: TextStyle(fontSize: 20),
),
),
),
);
}
// will return a widget used as an indicator for the drop position
Widget _buildDropPreview(BuildContext context, String value) {
return Card(
color: Colors.lightBlue[200],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
value,
style: TextStyle(fontSize: 20),
),
),
);
}
// builds DragTargets used as separators between list items/widgets for list A
Widget _buildDragTargetsA(BuildContext context, int index) {
return DragTarget<String>(
// builder responsible to build a widget based on whether there is an item being dropped or not
builder: (context, candidates, rejects) {
return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
Container(
width: 5,
height: 5,
);
},
// condition on to accept the item or not
onWillAccept: (value)=>!listA.contains(value),
// what to do when an item is accepted
onAccept: (value) {
setState(() {
listA.insert(index + 1, value);
listB.remove(value);
});
},
);
}
// builds drag targets for list B
Widget _buildDragTargetsB(BuildContext context, int index) {
return DragTarget<String>(
builder: (context, candidates, rejects) {
return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
Container(
width: 5,
height: 5,
);
},
onWillAccept: (value)=>!listB.contains(value),
onAccept: (value) {
setState(() {
listB.insert(index + 1, value);
listA.remove(value);
});
},
);
}
}
以下部分负责为ListB构建Draggable Widget,这个Draggable Widget会根据索引显示列表的值,并将列表项值设置为可拖动的数据,以便dragTarget可以处理拖动。
// builds the widgets for List B items
Widget _buildListBItems(BuildContext context, int index) {
return Draggable<String>(
// the value of this draggable is set using data
data: listB[index],
// the widget to show under the users finger being dragged
feedback: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listB[index],
style: TextStyle(fontSize: 20),
),
),
),
// what to display in the child's position when being dragged
childWhenDragging: Container(
color: Colors.grey,
width: 40,
height: 40,
),
// widget in idle state
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
listB[index],
style: TextStyle(fontSize: 20),
),
),
),
);
}
这部分代码将处理来自 listB 的可拖动对象(如果您修改代码,也可以是 listA),它将根据它们的值接受/拒绝可拖动对象,如果可以接受可拖动对象,它将显示一个小部件,并且它将setState 与修改后的列表,以便创建新状态并更改 ui。
// builds DragTargets used as separators between list items/widgets for list A
Widget _buildDragTargetsA(BuildContext context, int index) {
return DragTarget<String>(
// builder responsible to build a widget based on whether there is an item being dropped or not
builder: (context, candidates, rejects) {
return candidates.length > 0 ? _buildDropPreview(context, candidates[0]):
Container(
width: 5,
height: 5,
);
},
// condition on to accept the item or not
onWillAccept: (value)=>!listA.contains(value),
// what to do when an item is accepted
onAccept: (value) {
setState(() {
listA.insert(index + 1, value);
listB.remove(value);
});
},
);
}
最后在这段代码中,正如我上面提到的,它只接受从列表 A 到列表 B 的可拖动对象,反之亦然,但它不接受从同一个列表到同一个列表的可拖动对象。除此之外,还有一些我没有考虑但你可以考虑的情况,这些情况是:
- 将一个项目放在列表的顶部
- 将一个项目放在列表的底部
- 将项目从列表移动到空列表
希望我的回答有帮助!
推荐阅读
- makefile - 使用带有通配符目标的 nmake
- ios - 如何设置...在 mopub ios sdk 中初始化多个单元 id
- python-3.x - 熊猫数据框中的布尔索引
- java - AoC 第 8 天问题(Java)
- javascript - 如何在 html 中有一个开放日期选择器
- python - Python:使用 for 循环在 numpy 向量(包含随机 0,1,2 数字)中搜索序列 (1,1,2):
- r - Create new column with vector values based on groups from another character column
- c# - 在 MVC 网络核心中使用 Rotativa pdf 显示动态标题
- animation - 带有透明背景的 gif 动画
- python - 如何在急切执行模式下获取 keras 模型的可训练变量?