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

ListView内可拖拽元素的拖放垂直偏移错误问题

解决ListView中Draggable元素放下时的垂直偏移问题

嘿,我完全懂你遇到的这个拖拽偏移糟心事——手动减100px确实是个临时凑活的办法,根本不是长久之计。让我帮你揪出问题根源,给你一个靠谱的解决方案!

问题到底出在哪?

你遇到的垂直偏移,本质是坐标系统不匹配导致的:

  • Draggable的onDraggableCanceled回调返回的offset是全局屏幕坐标(以整个设备屏幕左上角为原点);
  • 而Positioned组件的top/left属性是相对于父容器Stack的局部坐标(以Stack的左上角为原点)。

当ListView滚动时,Stack的位置会相对于屏幕产生偏移,这时候直接把全局坐标赋值给Positioned的top,就会把ListView的滚动偏移也包含进去,自然就导致元素放下时位置偏下了。

正确解决方案:全局坐标转局部坐标

我们需要把回调里的全局坐标,转换为相对于Stack容器的局部坐标。可以通过RenderBox的globalToLocal方法轻松实现,具体修改如下:

修改DragBoxState中的onDraggableCanceled回调逻辑:

onDraggableCanceled: (velocity, offset) {
  // 获取当前DragBox所在父级Stack的渲染对象
  final RenderBox stackRenderBox = context.findAncestorRenderObjectOfType<RenderBox>()!;
  // 将全局屏幕坐标转换为Stack的局部坐标
  final Offset localOffset = stackRenderBox.globalToLocal(offset);
  
  setState(() {
    position = localOffset;
  });
}

完整修正后的代码

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Drag and drop bug"),
        ),
        body: App(),
      ),
    );
  }
}
class App extends StatefulWidget {
  @override
  AppState createState() => AppState();
}
class AppState extends State<App> {
  Color caughtColor = Colors.grey;
  Container spacer({double height: 400, Color color: Colors.blue}) {
    return Container(
      color: color,
      height: height,
    );
  }
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        spacer(color: Colors.amber),
        Container(
          height: 400,
          child: Stack(
            children: <Widget>[
              DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
              DragBox(Offset(200.0, 0.0), 'Box Two', Colors.orange),
              DragBox(Offset(300.0, 0.0), 'Box Three', Colors.lightGreen),
            ],
          ),
        ),
        spacer(color: Colors.cyan)
      ],
    );
  }
}
class DragBox extends StatefulWidget {
  final Offset initPos;
  final String label;
  final Color itemColor;
  DragBox(this.initPos, this.label, this.itemColor);
  @override
  DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
  Offset position = Offset(0.0, 0.0);
  @override
  void initState() {
    super.initState();
    position = widget.initPos;
  }
  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: position.dx,
      top: position.dy,
      child: Draggable(
        data: widget.itemColor,
        child: Container(
          width: 100.0,
          height: 100.0,
          color: widget.itemColor,
          child: Center(
            child: Text(
              widget.label,
              style: TextStyle(
                color: Colors.white,
                decoration: TextDecoration.none,
                fontSize: 20.0,
              ),
            ),
          ),
        ),
        onDraggableCanceled: (velocity, offset) {
          // 获取Stack的渲染对象并转换坐标
          final RenderBox stackRenderBox = context.findAncestorRenderObjectOfType<RenderBox>()!;
          final Offset localOffset = stackRenderBox.globalToLocal(offset);
          
          setState(() {
            position = localOffset;
          });
        },
        feedback: Container(
          width: 120.0,
          height: 120.0,
          color: widget.itemColor.withOpacity(0.5),
          child: Center(
            child: Text(
              widget.label,
              style: TextStyle(
                color: Colors.white,
                decoration: TextDecoration.none,
                fontSize: 18.0,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

为什么这个方法管用?

context.findAncestorRenderObjectOfType<RenderBox>()会精准定位到当前DragBox所在的父级Stack的渲染对象,然后调用globalToLocal方法,自动把全局屏幕坐标转换成相对于Stack左上角的局部坐标。这样不管ListView怎么滚动,计算出来的位置都是准确的,完全不需要手动调整像素值这种hack手段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:53:13