Flutter中如何在两个相邻Widget间渲染可拖动的中间Widget?
解决方案
一、正确获取Widget构建后尺寸的规范方法
你之前用WidgetsBinding.instance.addPostFrameCallback()获取高度为0,大概率是因为RenderBox还未完成布局绑定。以下两种方案更符合Flutter设计理念,无需依赖延迟:
1. GlobalKey + addPostFrameCallback 精准获取真实尺寸
确保将GlobalKey绑定到目标Widget,在当前帧布局完成后读取尺寸:
final GlobalKey _upperBoxKey = GlobalKey(); double _upperBoxHeight = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _getUpperBoxSize(); }); } void _getUpperBoxSize() { final RenderBox renderBox = _upperBoxKey.currentContext!.findRenderObject() as RenderBox; setState(() { _upperBoxHeight = renderBox.size.height; }); } // 线缆盒上部Widget绑定key Widget _buildUpperBox() { return Container( key: _upperBoxKey, color: Colors.grey[300], child: Text("线缆盒上部"), // 自适应高度场景也能正确获取 ); }
2. LayoutBuilder 动态读取父容器约束
如果线缆盒上部的高度由父容器约束决定,可直接通过LayoutBuilder拿到实时约束:
Widget _buildUpperBox() { return LayoutBuilder( builder: (context, constraints) { setState(() { _upperBoxHeight = constraints.maxHeight; }); return Container( height: constraints.maxHeight, color: Colors.grey[300], child: Text("线缆盒上部"), ); }, ); }
二、结合Stack+Positioned的布局实现方案
核心思路是通过Stack控制层级,让音频插孔穿插在上下线缆盒之间,同时用Positioned将下部精准定位到上部底部:
class AudioJackDemo extends StatefulWidget { @override _AudioJackDemoState createState() => _AudioJackDemoState(); } class _AudioJackDemoState extends State<AudioJackDemo> { final GlobalKey _upperBoxKey = GlobalKey(); double _upperBoxHeight = 0; Offset _jackOffset = Offset(0, 0); // 音频插孔位置 @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _initJackPosition(); }); } void _initJackPosition() { final RenderBox renderBox = _upperBoxKey.currentContext!.findRenderObject() as RenderBox; setState(() { _upperBoxHeight = renderBox.size.height; // 初始位置让插孔对齐上部底部 _jackOffset = Offset( MediaQuery.of(context).size.width / 2 - 20, _upperBoxHeight - 20, ); }); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 1. 底层:线缆盒上部 Container( key: _upperBoxKey, height: 150, // 自适应场景可移除,由内部内容决定高度 color: Colors.grey[300], alignment: Alignment.center, child: Text("线缆盒上部"), ), // 2. 中层:可拖动音频插孔(穿插在上下盒之间) Positioned( left: _jackOffset.dx, top: _jackOffset.dy, child: GestureDetector( onPanUpdate: (details) { setState(() { _jackOffset += details.delta; // 限制拖动范围,模拟插入逻辑 _jackOffset = Offset( _jackOffset.dx.clamp(20, MediaQuery.of(context).size.width - 40), _jackOffset.dy.clamp(_upperBoxHeight - 40, _upperBoxHeight + 80), ); }); }, child: Container( width: 40, height: 80, color: Colors.black, child: Align( alignment: Alignment.bottomCenter, child: Container(width: 20, height: 60, color: Colors.grey[700]), ), ), ), ), // 3. 顶层:线缆盒下部(紧贴上部) if (_upperBoxHeight > 0) Positioned( top: _upperBoxHeight, left: 0, right: 0, height: 150, child: Container( color: Colors.grey[400], alignment: Alignment.center, child: Text("线缆盒下部"), ), ), ], ), ); } }
关键说明
- 层级控制:Stack子Widget的顺序即层级顺序,上部在底层、插孔在中间、下部在顶层,拖动时插孔会被下部遮挡,完美模拟插入效果。
- 拖动逻辑:用
GestureDetector.onPanUpdate实现自定义拖动,比Draggable更灵活,完全可控位置边界。 - 尺寸稳定性:通过
GlobalKey或LayoutBuilder获取的尺寸是布局完成后的真实值,避免了Future.delayed的不可靠性。
内容的提问来源于stack exchange,提问作者Taylor Brown
相关产品推荐
相关产品推荐

