Flutter交错网格中如何通过拖拽单角实现容器固定对角的尺寸调整?
实现Staggered Grid View容器的单角拖拽调整功能
核心思路
Staggered Grid的布局依赖crossAxisCellCount和mainAxisCellCount两个参数控制容器尺寸,因此拖拽调整本质是动态修改这两个参数。我们选择固定容器左上角,拖拽右下角实现单向扩展,确保对角位置不变,仅拖动方向扩展容器大小。需要完成:
- 添加拖拽手柄组件,监听触摸手势
- 计算拖拽位移对应的单元格数量变化
- 实时更新数据并刷新UI
完整实现代码
1. 拖拽手柄与尺寸调整逻辑
class ResizeHandle extends StatefulWidget { final int index; final Function(int, int) onResize; final double cellWidth; final double cellHeight; const ResizeHandle({ super.key, required this.index, required this.onResize, required this.cellWidth, required this.cellHeight, }); @override State<ResizeHandle> createState() => _ResizeHandleState(); } class _ResizeHandleState extends State<ResizeHandle> { late PanGestureRecognizer _panGesture; Offset _startOffset = Offset.zero; int _initialColumn = 0; int _initialRow = 0; @override void initState() { super.initState(); _panGesture = PanGestureRecognizer() ..onStart = _onPanStart ..onUpdate = _onPanUpdate; } void _onPanStart(DragStartDetails details) { _startOffset = details.globalPosition; final item = (context.findAncestorStateOfType<_MyHomePageState>())!.value[widget.index]; _initialColumn = item['column']; _initialRow = item['row']; } void _onPanUpdate(DragUpdateDetails details) { final offsetDelta = details.globalPosition - _startOffset; // 计算位移对应的单元格数量 int newColumn = _initialColumn + (offsetDelta.dx / widget.cellWidth).floor(); int newRow = _initialRow + (offsetDelta.dy / widget.cellHeight).floor(); // 边界限制:最小1列/行,横向不超过总列数4 newColumn = newColumn.clamp(1, 4); newRow = newRow.clamp(1, double.infinity.toInt()); // 回调更新数据 widget.onResize(newColumn, newRow); } @override Widget build(BuildContext context) { return Positioned( right: 4, bottom: 4, child: GestureDetector( behavior: HitTestBehavior.opaque, recognizer: _panGesture, child: Container( width: 20, height: 20, decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(4), ), child: const Icon(Icons.drag_handle, size: 16, color: Colors.black), ), ), ); } @override void dispose() { _panGesture.dispose(); super.dispose(); } }
2. 修改原页面代码
集成拖拽手柄,添加单元格尺寸计算与数据更新逻辑:
class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { List<Map<String, dynamic>> value = [ {'height': 100.0, 'width': 50.0, 'color': Colors.red, 'column': 2, 'row': 1}, {'height': 200.0, 'width': 100.0, 'color': Colors.green, 'column': 2, 'row': 2}, {'height': 100.0, 'width': 200.0, 'color': Colors.blue, 'column': 1, 'row': 1}, {'height': 50.0, 'width': 50.0, 'color': Colors.orange, 'column': 1, 'row': 1}, {'height': 150.0, 'width': 50.0, 'color': Colors.teal, 'column': 2, 'row': 3}, {'height': 200.0, 'width': 200.0, 'color': Colors.yellow, 'column': 1, 'row': 1}, {'height': 100.0, 'width': 50.0, 'color': Colors.black, 'column': 1, 'row': 1}, {'height': 100.0, 'width': 50.0, 'color': Colors.red, 'column': 2, 'row': 2}, ]; late double _cellWidth; late double _cellHeight; @override void didChangeDependencies() { super.didChangeDependencies(); // 计算单个单元格的宽高(减去间距) final screenWidth = MediaQuery.of(context).size.width; const crossAxisCount = 4; const spacing = 4.0; _cellWidth = (screenWidth - (crossAxisCount + 1) * spacing) / crossAxisCount; _cellHeight = _cellWidth; // 保持宽高比1:1,可按需调整 } void _updateItemSize(int index, int newColumn, int newRow) { setState(() { value[index]['column'] = newColumn; value[index]['row'] = newRow; // 同步更新容器宽高 value[index]['width'] = newColumn * _cellWidth; value[index]['height'] = newRow * _cellHeight; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Demo"), ), body: Container( padding: const EdgeInsets.all(4), child: StaggeredGrid.count( crossAxisCount: 4, mainAxisSpacing: 4, crossAxisSpacing: 4, children: List.generate(value.length, (index) { return StaggeredGridTile.count( crossAxisCellCount: value[index]['column'], mainAxisCellCount: value[index]['row'], child: Container( margin: const EdgeInsets.all(5), child: Stack( fit: StackFit.expand, children: [ Container(color: value[index]['color']), // 保留原有更多按钮 Positioned( right: 4, top: 5, child: InkWell( onTap: () async { final result = await Navigator.push(context, MaterialPageRoute(builder: (ctx) => NewScreen(value: value[index]))); value[index] = result; setState(() {}); }, child: const Icon(Icons.more_vert, size: 30, color: Colors.white), ), ), // 添加拖拽手柄 ResizeHandle( index: index, onResize: (newColumn, newRow) => _updateItemSize(index, newColumn, newRow), cellWidth: _cellWidth, cellHeight: _cellHeight, ), ], ), ), ); }), ), ), ); } }
关键说明
- 拖拽逻辑:通过
PanGestureRecognizer监听拖拽位移,转换为单元格数量变化,确保拖拽方向仅扩展容器 - 尺寸联动:单元格尺寸基于屏幕宽度动态计算,保证拖拽后容器宽高与单元格数量匹配
- 边界控制:限制单元格数量最小为1,横向不超过总列数4,避免布局错乱
- 固定对角:默认固定左上角,拖拽右下角实现单向扩展,符合需求
内容的提问来源于stack exchange,提问作者ajinkya aher
相关产品推荐
相关产品推荐

