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

Flutter:使用LongPressDraggable拖拽时StreamBuilder流数据消失

问题原因分析

出现拖拽时流数据消失的核心原因有两个:

  1. 同一个StatefulWidget实例被重复使用:你将widget.widgetItem(即TextStream)同时作为LongPressDraggable的child和feedback,Flutter不允许同一个widget实例同时存在于Widget树的多个位置,这会导致原TextStream的State被销毁,StreamBuilder的流订阅随之取消,拖拽结束后即使原widget回到树中,新创建的State重新订阅流,但如果EventChannel没有再次发送数据,就会显示默认的"Unknown"。
  2. 拖拽时原widget被完全移除:childWhenDragging设置为SizedBox(),拖拽过程中原位置的TextStream被移除出Widget树,其State被dispose,同样会导致流订阅丢失。
解决方案

1. 避免复用同一个Widget实例作为feedback

创建独立的widget实例作为feedback,同时用Material包裹以保证Overlay中的样式正常。

2. 保留原widget的State(避免拖拽时移除)

将childWhenDragging改为原widget的半透明版本,这样拖拽时原widget的State不会被销毁,流订阅得以保持。

3. 将Stream转换为广播流

确保多订阅场景下能正常接收数据,避免单订阅流取消后无法再次获取数据。


修改后的代码

widget_view_model.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class WidgetViewModel extends ChangeNotifier {
  static const textEvent = EventChannel("com.example.example");
  late final Stream<String> textStream;

  WidgetViewModel() {
    // 转换为广播流,支持多订阅
    textStream = textEvent.receiveBroadcastStream()
        .distinct()
        .map((event) => event as String)
        .asBroadcastStream();
  }
}

base_widget.dart

import 'package:flutter/material.dart';

class BaseWidget extends StatefulWidget {
  final Widget widgetItem;

  const BaseWidget({Key? key, required this.widgetItem}) : super(key: key);

  @override
  State<BaseWidget> createState() => _BaseWidgetState();
}

class _BaseWidgetState extends State<BaseWidget> {
  double _x = 0.0;
  double _y = 0.0;

  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: _x,
      top: _y,
      child: LongPressDraggable(
        // 创建独立的feedback实例,避免复用同一个widget
        feedback: Material(
          child: widget.widgetItem,
        ),
        // 拖拽时原位置显示半透明的原widget,保留State
        childWhenDragging: Opacity(
          opacity: 0.3,
          child: widget.widgetItem,
        ),
        onDragEnd: (details) {
          setState(() {
            // 使用localPosition获取相对于Stack的局部坐标,减去一半宽高让中心对齐
            _x = details.localPosition.dx - 50;
            _y = details.localPosition.dy - 50;
          });
        },
        child: widget.widgetItem,
      ),
    );
  }
}

额外优化:修正拖拽位置偏移

原代码中使用details.offset是全局坐标,会导致拖拽后位置偏移,改为details.localPosition获取相对于父组件(Stack)的坐标,再减去widget的一半宽高,让拖拽结束后widget的中心落在松手位置。


内容的提问来源于stack exchange,提问作者Kumar Sri Harsh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 00:57:01