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

如何在宽度超屏的可滚动Flutter Widget中实现子项拖拽定位

Flutter 可滚动容器内子组件拖拽坐标适配方案

问题原因

Draggable组件onDragEnd回调返回的details.offset是全局屏幕坐标,而Stack内部Positioned组件使用的是Stack本身的局部坐标系,两者基准不一致,同时水平滚动产生的偏移也会导致全局坐标无法直接匹配局部定位需求。

解决方案

通过GlobalKey绑定Stack组件,将拖拽结束获取的全局坐标转换为Stack内部的相对坐标,无需手动处理滚动偏移、状态栏/AppBar高度等变量,适配性更强。

修改后完整代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drag Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({super.key});

  // 定义GlobalKey绑定Stack
  final GlobalKey stackKey = GlobalKey();

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('drag demo'),
      ),
      body: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Container(
          width: 5000,
          color: Colors.red.withAlpha(50),
          // 给Stack绑定key
          child: Stack(
            key: widget.stackKey,
            children: [
              Item(text: '1', x: 100, y: 300, stackKey: widget.stackKey),
              Item(text: '2', x: 300, y: 300, stackKey: widget.stackKey),
              Item(text: '3', x: 500, y: 300, stackKey: widget.stackKey),
              Item(text: '4', x: 700, y: 300, stackKey: widget.stackKey),
              Item(text: '5', x: 900, y: 300, stackKey: widget.stackKey),
              Item(text: '6', x: 1100, y: 300, stackKey: widget.stackKey),
              Item(text: '7', x: 1300, y: 300, stackKey: widget.stackKey),
            ],
          ),
        ),
      ),
    );
  }
}

class Item extends StatefulWidget {
  final String text;
  final double x;
  final double y;
  // 接收Stack的GlobalKey
  final GlobalKey stackKey;

  const Item({
    Key? key,
    required this.text,
    required this.x,
    required this.y,
    required this.stackKey,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => ItemState();
}

class ItemState extends State<Item> {
  late double cx;
  late double cy;

  @override
  void initState() {
    super.initState();
    cx = widget.x;
    cy = widget.y;
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: cx,
      top: cy - 80,
      child: Draggable(
        feedback: Center(child: Text(widget.text)),
        child: Container(
          child: Text(
            widget.text,
            textScaleFactor: 2,
          ),
        ),
        childWhenDragging: Container(),
        onDragEnd: (details) {
          // 获取Stack对应的渲染对象
          final RenderBox stackRenderBox = widget.stackKey.currentContext?.findRenderObject() as RenderBox;
          // 全局坐标转Stack局部坐标
          final localOffset = stackRenderBox.globalToLocal(details.offset);
          setState(() {
            cx = localOffset.dx;
            // 对应原有减去80的逻辑,保持坐标一致
            cy = localOffset.dy + 80;
          });
        },
      ),
    );
  }
}

注意事项

如果不需要保留原有cy - 80的偏移调整,可直接把localOffset.dy赋值给cy,同时将Positioned的top属性改为top: cy即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 10:39:02