Flutter中Draggable用带GlobalKey组件时重复Key错误解决方法
解决Draggable组件使用GlobalKey导致重复键错误的方案
你遇到的问题本质是同一个带GlobalKey的Widget实例不能同时挂载到Widget树的两个节点上——拖动时Draggable的child(原位置组件)和feedback(拖动跟随的组件)会在同一帧内同时存在,而你复用了同一个SFWidget实例,导致GlobalKey冲突。
下面提供两种可行的解决方案:
方案一:将状态与Widget分离,共享状态而非复用Widget实例
把SFWidget的状态提取到独立的状态容器中,让child和feedback使用不同的Widget实例,但共享同一状态:
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class SFWidget extends StatelessWidget { final ValueNotifier<String> strNotifier; const SFWidget({super.key, required this.strNotifier}); @override Widget build(BuildContext context) { return ValueListenableBuilder<String>( valueListenable: strNotifier, builder: (context, value, child) { return Text(value); }, ); } } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final strNotifier = ValueNotifier<String>("str"); @override Widget build(BuildContext context) { return Scaffold( body: Draggable( child: SFWidget(strNotifier: strNotifier), feedback: SFWidget(strNotifier: strNotifier), childWhenDragging: const SizedBox.shrink(), data: strNotifier, ), floatingActionButton: FloatingActionButton( onPressed: () => strNotifier.value = "new str", child: const Icon(Icons.edit), ), ); } }
这个方案通过ValueNotifier共享状态,child和feedback是两个独立的SFWidget实例,各自监听同一个状态容器,既避免了GlobalKey冲突,又能保持状态同步。
方案二:拖动时动态生成Feedback,同步原组件状态
如果需要保留StatefulWidget的封装,可以在拖动时创建新的SFWidget实例,并将原组件的状态传递过去:
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class SFWidget extends StatefulWidget { final String? initialStr; const SFWidget({super.key, this.initialStr}); @override State<SFWidget> createState() => SFWidgetState(); } class SFWidgetState extends State<SFWidget> { late String str; @override void initState() { super.initState(); str = widget.initialStr ?? "str"; } void edit(String newstr) { setState(() { str = newstr; }); } @override Widget build(BuildContext context) { return Text(str); } } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends State<MyHomePage> { final GlobalKey<SFWidgetState> widgetKey = GlobalKey<SFWidgetState>(); @override Widget build(BuildContext context) { return Scaffold( body: Draggable( child: SFWidget(key: widgetKey), // 拖动时生成新的SFWidget,同步原组件的状态 feedback: SFWidget(initialStr: widgetKey.currentState?.str), childWhenDragging: const SizedBox.shrink(), data: widgetKey.currentState, ), floatingActionButton: FloatingActionButton( onPressed: () => widgetKey.currentState?.edit("new str"), child: const Icon(Icons.edit), ), ); } }
这个方案中,feedback是每次拖动时新创建的SFWidget实例,通过initialStr传递原组件的当前状态,既避免了GlobalKey重复,又能让拖动的反馈组件和原组件状态一致。
额外说明
- 不要复用同一个带GlobalKey的Widget实例到Widget树的多个位置,这是Flutter的核心规则之一。
- 如果需要在拖动结束后保留状态,只需要确保
data传递的是状态容器(比如方案一的ValueNotifier)或状态对象(方案二的SFWidgetState),在DragTarget中接收后即可继续操作状态。
内容的提问来源于stack exchange,提问作者Daniel Z.
相关产品推荐
相关产品推荐

