Flutter 4x3拼图拖拽后位置异常问题求助
问题解决:Android TV拼图应用拖拽后位置异常
问题根源
你的代码里有两个关键问题导致拖拽松开后拼图块跑到随机位置:
- 所有拼图块的
onDragEnd都调用同一个updatePosition方法,修改的是单个position变量,而非对应索引的positions列表项 details.offset是屏幕全局坐标,没有转换为Stack容器内的局部坐标,导致位置计算完全错误
修复步骤
- 新增方法针对当前拖拽的拼图索引,更新对应
positions列表项 - 通过全局Key获取Stack的渲染对象,把全局坐标转换成容器内的局部坐标
- 移除无用的单个
position变量,统一用positions列表管理所有拼图位置 - 调整拖拽反馈和占位容器的尺寸,避免布局偏移
修复后的完整代码
import 'package:flutter/material.dart'; import 'dart:math'; void main() { runApp(PuzzleGridApp()); } class PuzzleGridApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('ROMPECABEZAS! :D'), ), body: PuzzleGrid(), ), ); } } class PuzzleGrid extends StatefulWidget { @override _PuzzleGridState createState() => _PuzzleGridState(); } class _PuzzleGridState extends State<PuzzleGrid> { List<Offset> positions = List.generate(12, (index) => const Offset(0, 0)); final GlobalKey _stackKey = GlobalKey(); final List<Image> puzzlePieces = [ Image.asset('images/puzzle1_0.png', width: 100, height: 100), Image.asset('images/puzzle1_1.png', width: 100, height: 100), Image.asset('images/puzzle1_2.png', width: 100, height: 100), Image.asset('images/puzzle1_3.png', width: 100, height: 100), Image.asset('images/puzzle1_4.png', width: 100, height: 100), Image.asset('images/puzzle1_5.png', width: 100, height: 100), Image.asset('images/puzzle1_6.png', width: 100, height: 100), Image.asset('images/puzzle1_7.png', width: 100, height: 100), Image.asset('images/puzzle1_8.png', width: 100, height: 100), Image.asset('images/puzzle1_9.png', width: 100, height: 100), Image.asset('images/puzzle1_10.png', width: 100, height: 100), Image.asset('images/puzzle1_11.png', width: 100, height: 100), ]; @override void initState() { super.initState(); setInitialPositions(); } void setInitialPositions() { Random random = Random(); for (int i = 0; i < positions.length; i++) { double x = random.nextDouble() * 350; double y = random.nextDouble() * 250; positions[i] = Offset(x + 425, y); } } double prevScale = 1; double scale = 1; void updateScale(double zoom) => setState(() => scale = prevScale * zoom); void commitScale() => setState(() => prevScale = scale); void updatePiecePosition(int index, Offset globalOffset) { // 获取Stack容器的渲染对象,转换全局坐标为局部坐标 RenderBox? stackBox = _stackKey.currentContext?.findRenderObject() as RenderBox?; if (stackBox != null) { Offset localOffset = stackBox.globalToLocal(globalOffset); // 调整偏移,让拼图中心对齐拖拽终点 Offset adjustedOffset = Offset( localOffset.dx - 50, // 50是拼图宽度的一半(100/2) localOffset.dy - 50, // 50是拼图高度的一半(100/2) ); setState(() { positions[index] = adjustedOffset; }); } } @override Widget build(BuildContext context) { return Center( child: Container( width: MediaQuery.of(context).size.width * 0.95, height: MediaQuery.of(context).size.height * 0.85, color: Colors.black54, child: GestureDetector( onScaleUpdate: (details) => updateScale(details.scale), onScaleEnd: (_) => commitScale(), child: Stack( key: _stackKey, children: [ CustomPaint( size: const Size(400, 300), painter: GridPainter(), ), ...List.generate(12, (index) { return Positioned( left: positions[index].dx, top: positions[index].dy, child: Draggable<int>( maxSimultaneousDrags: 1, data: index, feedback: Transform.scale( scale: scale, child: puzzlePieces[index], ), childWhenDragging: Container( width: 100, height: 100, color: Colors.amber, ), onDragEnd: (details) => updatePiecePosition(index, details.offset), child: Transform.scale( scale: scale, child: puzzlePieces[index], ), ), ); }), ], ), ), ), ); } } class GridPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 2; double cellWidth = size.width / 4; double cellHeight = size.height / 3; for (int i = 0; i <= 4; i++) { canvas.drawLine( Offset(cellWidth * i, 0), Offset(cellWidth * i, size.height), paint, ); } for (int i = 0; i <= 3; i++) { canvas.drawLine( Offset(0, cellHeight * i), Offset(size.width, cellHeight * i), paint, ); } } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
额外说明
- 后续添加重置按钮时,直接调用
setInitialPositions()并触发setState即可恢复初始随机位置 - 若需要添加位置对齐验证,可在
updatePiecePosition里判断局部坐标是否落在网格单元格范围内,再调整到对应单元格的中心位置
内容的提问来源于stack exchange,提问作者Iván David
相关产品推荐
相关产品推荐

