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

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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 06:06:28