You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的逻辑

方法二:通过手势类型区分(长按触发重排,短按触发自定义拖拽)

如果需要同一方向拖拽同时支持两种功能,可以通过手势识别区分长按和短按:

  1. 在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;
      },
      // 其他原有配置保持不变...
    ),
  );
}
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:10:11