已知问题:使用layout builder包裹获取子组件大小,会较大概率出现卡死,建议包裹父组件计算后传递进来;首尾滚动速度不一致;
核心组件,把这个套在你原有的listView或者GirdView等可滚动组件的child上
class DragTargetView extends StatefulWidget {
final T data;
final Widget Function(BuildContext contex) itemBuilder;
final Widget feedback;
final Function(T moveData, T toData) exchangeItem;
final DragTargetAccept? onAccept;
final VoidCallback? onDragStarted;
final DraggableCanceledCallback? onDraggableCanceled;
const DragTargetView({
super.key,
required this.data,
required this.itemBuilder,
required this.feedback,
required this.exchangeItem,
this.onDragStarted,
this.onDraggableCanceled,
this.onAccept
});
@override
State createState() => _DragTargetViewState();
}
class _DragTargetViewState extends State> {
EdgeDraggingAutoScroller? autoScroller;
late ScrollableState scrollable;
Axis get scrollDirection => axisDirectionToAxis(scrollable.axisDirection);
bool get reverse => scrollable.axisDirection == AxisDirection.up || scrollable.axisDirection == AxisDirection.left;
Offset dragPosition = Offset.zero;
Offset dragOffset = Offset.zero;
@override
Widget build(BuildContext context) {
double maxHeight = 109;
double maxWidth = 109;
scrollable = Scrollable.of(context)!;
autoScroller = EdgeDraggingAutoScroller(
scrollable,
onScrollViewScrolled: () {
autoScroller?.startAutoScrollIfNecessary(dragTargetRect(maxWidth, maxHeight));
}
);
return LongPressDraggable(
data: widget.data,
feedback: widget.feedback,
childWhenDragging: null,
onDragStarted: widget.onDragStarted,
onDragUpdate: (DragUpdateDetails details) {
dragPosition = details.localPosition;
dragOffset = details.delta;
autoScroller?.startAutoScrollIfNecessary(dragTargetRect(maxWidth, maxHeight));
},
onDraggableCanceled: widget.onDraggableCanceled,
onDragEnd: (details) {
autoScroller?.startAutoScrollIfNecessary(dragTargetRect(maxWidth, maxHeight));
},
child: DragTarget(
builder: (BuildContext context, List candidateData, List rejectedData) {
bool isContains = candidateData.contains(widget.data);
if (isContains) {
return Container();
}
return widget.itemBuilder(context);
},
onWillAccept: (moveData) {
var accept = moveData != null;
if (accept) {
widget.exchangeItem(moveData, widget.data);
}
return accept;
},
onAccept: widget.onAccept,
),
);
}
Rect dragTargetRect(double width, double height) {
Offset origin = dragPosition - dragOffset;
if (scrollDirection == Axis.horizontal) {
origin = Offset(dragPosition.dx < width ? -1 : origin.dx, origin.dy);
} else if (scrollDirection == Axis.vertical) {
origin = Offset(origin.dx, dragPosition.dy < height ? -1 : origin.dy);
}
print("dragTargetRect: dragPosition= $dragPosition, dragOffset= $dragOffset, origin= $origin");
return Rect.fromLTWH(origin.dx, origin.dy, width, height);
}
}
使用示范
这里构建了一个可拖拽滚动列表
class SingleDragGridView extends StatefulWidget {
final List dataList;
final Widget Function(BuildContext contex, int index, T data) itemBuilder;
final SliverGridDelegate gridDelegate;
final Function(List newList) onChanged;
final ScrollPhysics? physics;
final bool? shrinkWrap;
final Axis? scrollDirection;
final EdgeInsetsGeometry? padding;
const SingleDragGridView({
super.key,
required this.gridDelegate,
required this.itemBuilder,
required this.dataList,
required this.onChanged,
this.physics,
this.shrinkWrap,
this.scrollDirection,
this.padding
});
@override
State createState() => _SingleDragGridViewState();
}
class _SingleDragGridViewState extends State> {
List dataList = [];
@override
void initState() {
super.initState();
dataList.addAll(widget.dataList);
}
@override
void didUpdateWidget(covariant SingleDragGridView oldWidget) {
super.didUpdateWidget(oldWidget);
if (dataList != widget.dataList) dataList = [...widget.dataList];
}
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: widget.padding,
gridDelegate: widget.gridDelegate,
physics: widget.physics,
shrinkWrap: widget.shrinkWrap ?? true,
scrollDirection: widget.scrollDirection ?? Axis.vertical,
itemBuilder: (context, index) {
return draggableItem(context, index);
},
itemCount: dataList.length,
);
}
Widget draggableItem(BuildContext context, int index) {
T data = dataList[index];
return DragTargetView(
data: data,
itemBuilder: (context) => widget.itemBuilder(context, index, data),
feedback: Transform(
transform: Matrix4.identity()..scale(1.1, 1.1),
alignment: Alignment.center,
child: widget.itemBuilder(context, index, data),
),
exchangeItem: exchangeItem,
);
}
// 重新排序
void exchangeItem(T moveData, T toData) {
setState(() {
int toIndex = dataList.indexOf(toData);
dataList.remove(moveData);
dataList.insert(toIndex, moveData);
});
widget.onChanged(dataList);
}
}
最终
@override
Widget build(BuildContext context) {
List list = [];
for (int i = 0;i<20;i++) {
list.add(i);
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleDragGridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
childAspectRatio: 1),
itemBuilder: (context, index, data) {
return Material(
child: Container(
width: 100,
height: 100,
color: Colors.green,
child: Text("$data"),
),
);
},
dataList: list,
onChanged: (newList) {
}
)
);
}