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

Flutter中Hover DragTarget时Draggable触发错误信息问题排查

问题根源与解决方案

嘿,我立刻就发现你代码里的问题啦!你的两个DragTarget组件共享了同一个isSuccessful和caughtData变量,而且还没通过setState()更新状态,这就是导致诡异现象的核心原因。

为什么会出问题?

你把isSuccessful和caughtData定义在整个Widget的build方法里,这意味着这两个变量属于整个父Widget的状态,不是每个DragTarget单独的状态。当第一个DragTarget接受拖拽后,isSuccessful变成true,caughtData被赋值,第二个DragTarget的builder会直接读取这个全局的变量值——哪怕它自己根本没接受任何拖拽,自然就会错误显示第一个目标的内容。

另外,你在onAccept里修改变量后没有调用setState(),这会导致UI无法及时刷新,后续的状态变化也不会正确反映到界面上。

解决方案:让每个DragTarget拥有独立状态

最清晰的方式是把每个DragTarget封装成独立的StatefulWidget,这样每个目标都有自己专属的状态变量,互相不会干扰。

步骤1:创建自定义的ChordDragTarget组件

class ChordDragTarget extends StatefulWidget {
  const ChordDragTarget({Key? key}) : super(key: key);

  @override
  State<ChordDragTarget> createState() => _ChordDragTargetState();
}

class _ChordDragTargetState extends State<ChordDragTarget> {
  bool isSuccessful = false;
  int? caughtData; // 使用可空类型更安全,避免空指针问题

  // 把播放逻辑抽出来,让代码更简洁
  void _playChordNotes(int data) {
    playSound(noteBrain.noteBank[data].note1);
    playSound(noteBrain.noteBank[data].note2);
    playSound(noteBrain.noteBank[data].note3);
    playSound(noteBrain.noteBank[data].note4);
  }

  @override
  Widget build(BuildContext context) {
    return DragTarget<int>(
      builder: (context, List<int> candidateData, rejectedData) {
        // 只有当状态成功且有捕获到数据时才显示按钮
        return isSuccessful && caughtData != null
            ? FlatButton(
                color: chordBrain.chordBank[caughtData!].buttoncolor,
                child: Text(chordBrain.chordBank[caughtData!].chord),
                onPressed: () => _playChordNotes(caughtData!),
              )
            : Container();
      },
      onWillAccept: (int data) {
        print('$data');
        return true;
      },
      onAccept: (int data) {
        print('$data');
        // 必须调用setState()更新状态,触发UI重建
        setState(() {
          isSuccessful = true;
          caughtData = data;
        });
      },
    );
  }
}

步骤2:在原代码中替换DragTarget

现在把原来的两个DragTarget都换成我们自定义的ChordDragTarget,每个目标都会拥有独立的状态:

Widget build(BuildContext context) {
  return SafeArea(
    child: Column(
      children: <Widget>[
        Row(
          children: <Widget>[
            Expanded(
              child: Container(
                height: 70,
                width: 200,
                color: Colors.grey.shade600,
                child: const ChordDragTarget(), // 第一个独立的拖拽目标
              ),
            ),
            SizedBox(width: 8),
            Expanded(
              child: Container(
                height: 70,
                width: 200,
                color: Colors.grey.shade600,
                child: const ChordDragTarget(), // 第二个独立的拖拽目标
              ),
            ),
          ],
        ),
        // 你的其他组件...
      ],
    ),
  );
}

另一种快速方案:用StatefulBuilder包裹

如果不想单独写组件,也可以用StatefulBuilder给每个DragTarget包裹一层,让它们拥有局部独立状态:

Expanded(
  child: Container(
    height: 70,
    width: 200,
    color: Colors.grey.shade600,
    child: StatefulBuilder(
      builder: (context, setState) {
        bool isSuccessful = false;
        int? caughtData;
        return DragTarget<int>(
          builder: (context, candidateData, rejectedData) {
            return isSuccessful && caughtData != null
                ? FlatButton(
                    color: chordBrain.chordBank[caughtData!].buttoncolor,
                    child: Text(chordBrain.chordBank[caughtData!].chord),
                    onPressed: () {
                      playSound(noteBrain.noteBank[caughtData!].note1);
                      playSound(noteBrain.noteBank[caughtData!].note2);
                      playSound(noteBrain.noteBank[caughtData!].note3);
                      playSound(noteBrain.noteBank[caughtData!].note4);
                    },
                  )
                : Container();
          },
          onWillAccept: (data) {
            print('$data');
            return true;
          },
          onAccept: (data) {
            setState(() {
              isSuccessful = true;
              caughtData = data;
            });
          },
        );
      },
    ),
  ),
),

关键要点回顾

  • 每个DragTarget必须拥有独立的状态变量,不能和其他目标共享
  • 状态变化时必须调用setState(),确保UI能正确刷新
  • 建议使用可空类型(比如int?)处理未捕获数据的情况,避免空指针异常

内容的提问来源于stack exchange,提问作者Matthias Bakker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 07:58:13