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

无限画布中拖拽节点出屏幕后Gesture Detector失效问题

问题修复:思维导图节点拖出屏幕后手势失效

问题根源

  1. 容器尺寸限制:画布中包裹节点的Stack尺寸等于屏幕大小,节点拖出屏幕后实际位置超出Stack边界,即使平移画布将节点拉回视觉范围内,GestureDetector的触摸检测仍受限于Stack原始边界,无法触发手势。
  2. 状态不同步:MyNode本地维护_widgetOffset,未与Cubit中的全局节点偏移完全同步,画布缩放平移时,本地状态无法映射到正确坐标,加剧手势检测错位。

修复步骤

1. 调整画布结构,解除尺寸限制

把包裹节点的Stack放到超大尺寸的Container中,关闭裁剪,确保节点无论拖多远都在可检测范围内:

// 画布中节点容器部分修改
Transform(
  transform: Matrix4.diagonal3Values(_scale, _scale, 1.0)
    ..translate(_offset.dx, _offset.dy),
  alignment: Alignment.center,
  child: Container(
    width: 10000,
    height: 10000,
    child: Stack(
      clipBehavior: Clip.none, // 关闭裁剪,保留超出容器的手势检测
      children: [
        for (int index = 0;
            index < context.read<NodeCubit>().nodeList.length;
            index++)
          MyNode(index: index),
      ],
    ),
  ),
),

2. 统一节点偏移状态,移除本地维护变量

让MyNode完全从Cubit读取节点偏移,不再维护本地状态,确保全局状态同步:

class _MyNodeState extends State<MyNode> {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<NodeCubit, NodeState>(
      builder: (context, state) {
        if (state is NodeUpdatedState) {
          final nodeList = state.nodes;
          final node = nodeList[widget.index!];
          return Positioned(
            left: node.offset.dx,
            top: node.offset.dy,
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onPanUpdate: (details) {
                // 直接基于Cubit中的节点偏移计算新位置
                final newOffset = Offset(
                  node.offset.dx + details.delta.dx,
                  node.offset.dy + details.delta.dy,
                );
                context.read<NodeCubit>().updateOffset(node.id, newOffset);
              },
              child: Container(
                height: node.height?.toDouble(),
                width: node.width?.toDouble(),
                decoration: BoxDecoration(
                  color: Colors.black87.withAlpha(200),
                  boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1))],
                  border: Border.all(
                    color: Colors.white.withOpacity(0.1),
                    strokeAlign: BorderSide.strokeAlignOutside,
                    width: 1.25,
                  ),
                  borderRadius: BorderRadius.circular(6),
                ),
                child: const Align(alignment: AlignmentDirectional.center),
              ),
            ),
          );
        } else {
          return const Text('Something went wrong...');
        }
      },
    );
  }
}

3. 适配缩放比例的拖动速度

画布缩放时,触摸的details.delta是缩放后的坐标,需要转换为原始比例,避免拖动速度随缩放变化:

// 在MyNode的onPanUpdate中修改
onPanUpdate: (details) {
  // 建议将画布_scale放入Cubit管理,这里临时通过上下文获取
  final scale = context.findAncestorStateOfType<MyMindMapWidgetState>()!._scale;
  final adjustedDelta = Offset(
    details.delta.dx / scale,
    details.delta.dy / scale,
  );
  final newOffset = Offset(
    node.offset.dx + adjustedDelta.dx,
    node.offset.dy + adjustedDelta.dy,
  );
  context.read<NodeCubit>().updateOffset(node.id, newOffset);
},

额外优化建议

  • 将画布的缩放、平移状态迁移到Cubit中,和节点状态统一管理,避免跨组件状态访问。
  • 尝试使用Flutter官方的InteractiveViewer组件替代自定义缩放平移逻辑,它已内置处理手势冲突、坐标转换等细节,稳定性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:27:04