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
相关产品推荐
相关产品推荐

