ReorderableListView嵌套Draggable后onReorder失效问题求助
问题:ReorderableListView与自定义Draggable组件共存时onReorder不触发
我实现了一个包含5个Draggable组件的ReorderableListView,Draggable功能正常,但ReorderableListView的onReorder函数无法触发。
ReorderableListView代码:
return Scaffold( body: ReorderableListView( onReorder: ((oldIndex, newIndex) { print('onReorder'); }), onReorderStart: (index) => print('reorder start'), scrollDirection: Axis.horizontal, children: [ for (final card in handCards) HandCard(key: ValueKey(card), card, player, handOwner), ], ), );
每个HandCard组件返回一个Draggable组件,请问是否有办法让两者功能同时正常运行?
HandCard组件代码:
class HandCard extends ConsumerStatefulWidget { HandCard(this.card, this.player, this.handOwner, {Key? key}) : super(key: key); String handOwner; dynamic card; Player player; @override _HandCardState createState() => _HandCardState(); } class _HandCardState extends ConsumerState<HandCard> { @override void initState() { isVisibled = true; super.initState(); print(isVisibled); } void setVisible() { setState(() { isVisibled = true; }); } late bool isVisibled; @override Widget build(BuildContext context) { var handCardPickProvider = ref.watch(handCardHighlightProvider(widget.card).notifier); var handCardPick = ref.watch(handCardHighlightProvider(widget.card)); var gameStateProvider = ref.watch(GameStateProvider); var handSize = ref.watch(handSizeProvider(widget.handOwner).state); return Draggable<HandCard>( onDragStarted: () { // change state of current cards in hand handSize.state = handSize.state - 1; }, onDragEnd: (details) { handSize.state = handSize.state + 1; }, onDragCompleted: () { setState(() { isVisibled = true; }); }, data: widget, childWhenDragging: SizedBox.shrink(), feedback: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.card.imageLink), fit: BoxFit.fill, ), ), width: 100, height: 150, child: Stack(children: [ (widget.card is CharacterCard) ? Positioned( right: 0, left: 60, child: Container( color: Colors.white, width: 40, height: 13, child: Center( child: Text( widget.card.power.toString(), style: TextStyle( color: Colors.black, fontSize: 10, fontWeight: FontWeight.bold), ), ), ), ) : const SizedBox.shrink(), Positioned( left: 2, child: Container( width: 20, height: 20, decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Center( child: Text( widget.card.cost.toString(), style: TextStyle(color: Colors.white, fontSize: 12), )), )) ]), ), child: (isVisibled) ? InkWell( onTap: () { print('click'); // If it is the counter move and the card has a counter effect we can use it if (gameStateProvider.gameSession!.moves.length != 0) { Move currentMove = gameStateProvider.gameSession! .moves[gameStateProvider.gameSession!.atMove - 1]; print(currentMove.moveType); // Handle on counter effect if (currentMove.moveType == 'on counter effect' && currentMove.toPlayer.id == widget.player.id) { handCardPickProvider.highlightCard(widget.card); } } }, child: Container( margin: EdgeInsets.all(1), decoration: BoxDecoration( boxShadow: [ (handCardPick) ? BoxShadow( color: Colors.white, spreadRadius: 1, blurRadius: 10) : BoxShadow( color: Colors.white, spreadRadius: 0, blurRadius: 0), ], image: DecorationImage( image: AssetImage(widget.card.imageLink), fit: BoxFit.fill, ), ), width: 100, height: 150, child: Stack(children: [ (widget.card is CharacterCard) ? Positioned( right: 0, left: 60, child: Container( color: Colors.white, width: 40, height: 13, child: Center( child: Text( widget.card.power.toString(), style: TextStyle( color: Colors.black, fontSize: 10, fontWeight: FontWeight.bold), ), ), ), ) : const SizedBox.shrink(), Positioned( left: 2, child: Container( width: 20, height: 20, decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Center( child: Text( widget.card.cost.toString(), style: TextStyle(color: Colors.white, fontSize: 12), )), )) ]), ), ) : SizedBox.shrink(), ); } }
解决方案
核心问题是自定义Draggable会拦截所有触摸拖拽事件,导致ReorderableListView无法触发自身的重排逻辑。以下两种方法可以实现两者共存:
方法一:通过拖拽方向区分触发逻辑
由于你的ReorderableListView是水平方向(scrollDirection: Axis.horizontal),重排操作是水平拖拽,而自定义Draggable通常是拖到其他垂直区域,可以给Draggable指定拖拽方向为垂直,让两者的拖拽事件不冲突:
修改HandCard中的Draggable配置,添加axis: Axis.vertical:
return Draggable<HandCard>( axis: Axis.vertical, // 新增:限制自定义拖拽为垂直方向 onDragStarted: () { handSize.state = handSize.state - 1; }, // 其他原有配置保持不变... );
这样操作时:
- 水平拖拽卡片 → 触发ReorderableListView的
onReorder - 垂直拖拽卡片 → 触发自定义Draggable的逻辑
方法二:通过手势类型区分(长按触发重排,短按触发自定义拖拽)
如果需要同一方向拖拽同时支持两种功能,可以通过手势识别区分长按和短按:
- 在HandCard中添加索引参数,并用
GestureDetector包裹Draggable,捕捉长按事件并手动触发Reorderable的重排:
// 修改HandCard构造函数,添加index参数 class HandCard extends ConsumerStatefulWidget { HandCard(this.card, this.player, this.handOwner, {required this.index, Key? key}) : super(key: key); String handOwner; dynamic card; Player player; final int index; // 新增索引参数 @override _HandCardState createState() => _HandCardState(); } // build方法中修改为: @override Widget build(BuildContext context) { // 原有代码... return GestureDetector( onLongPressStart: (details) { // 手动触发ReorderableListView的重排逻辑 ReorderableListView.maybeOf(context)?.startReorder(widget.index, details.globalPosition); }, child: Draggable<HandCard>( onDragStarted: () { // 这里只处理短按拖拽的逻辑 handSize.state = handSize.state - 1; }, // 其他原有配置保持不变... ), ); }
- 修改ReorderableListView的子项,传递索引:
children: [ for (int i = 0; i < handCards.length; i++) HandCard( key: ValueKey(handCards[i]), card: handCards[i], player: player, handOwner: handOwner, index: i, // 传递索引 ), ],
这种方式下:
- 长按卡片 → 触发ReorderableListView的重排
- 短按拖拽卡片 → 触发自定义Draggable的逻辑
额外注意事项
- 确保HandCard的
key是唯一且稳定的,当前用ValueKey(card)没问题,只要card对象有正确的==和hashCode实现。 - 如果使用方法二,需要确保ReorderableListView的上下文能被HandCard获取到,避免
maybeOf返回null。
内容的提问来源于stack exchange,提问作者daniel herzog
相关产品推荐
相关产品推荐

