Flutter DraggableScrollableSheet键盘弹出时重置为minSize问题
问题原因
- 软键盘弹出时会修改
MediaQuery.viewInsets.bottom值,触发页面整体重绘,DraggableScrollableSheet默认会在重绘时基于新的可视区域重新计算尺寸,结合你开启的snap自动吸附逻辑,尺寸计算出现偏差后会自动回弹到最近的锚点也就是minChildSize位置。 - 现有逻辑中通过
NotificationListener获取的extent值会在布局重绘的中间态被更新,干扰了顶部占位容器的高度计算,进一步加剧布局错乱。 - 没有对输入框聚焦、键盘弹出的场景做特殊处理,sheet状态没有被锁定,会响应默认的尺寸重算逻辑。
修复方案
- 新增输入框焦点监听,在键盘弹出时临时关闭snap吸附,主动将sheet展开到最大高度,避免回弹。
- 优化extent更新逻辑,键盘弹出状态下不接收布局重绘的中间态extent值,避免高度计算错乱。
- 适配键盘弹出时的页面高度,保证输入框不被遮挡。
- 键盘收起后恢复原有拖拽、吸附逻辑,不影响正常交互。
具体实现代码
首先在对应State类中新增必要的状态变量:
// 键盘弹出状态标记 bool _isKeyboardOpen = false; // 记录键盘弹出前的sheet位置,键盘收起时可按需恢复 double _lastExtentBeforeKeyboardShow = 0; // 输入框焦点监听节点 final FocusNode _inputFocusNode = FocusNode(); // 原有extent变量保留 late double extent;
在initState中添加焦点监听逻辑:
@override void initState() { super.initState(); extent = 0.175; // 初始值和initialChildSize保持一致 _inputFocusNode.addListener(() { setState(() { _isKeyboardOpen = _inputFocusNode.hasFocus; if (_isKeyboardOpen) { _lastExtentBeforeKeyboardShow = extent; } }); if (_isKeyboardOpen) { // 等待布局完成后执行展开动画,避免和键盘弹出动画冲突 WidgetsBinding.instance.addPostFrameCallback((_) { widget.draggableScrollableController.animateTo( heightFactorForBottomNavScreen, duration: const Duration(milliseconds: 220), curve: Curves.easeOutCubic, ); }); } }); } @override void dispose() { _inputFocusNode.dispose(); super.dispose(); }
修改原有布局代码,适配键盘弹出场景:
NotificationListener<DraggableScrollableNotification>( onNotification: (d) { // 键盘弹出时不更新extent,避免中间态值干扰布局计算 if (!_isKeyboardOpen) { extent = d.extent; } return true; }, child: DraggableScrollableSheet( initialChildSize: .175, minChildSize: .175, maxChildSize: heightFactorForBottomNavScreen, // 键盘弹出时临时关闭吸附,避免自动回弹 snap: !_isKeyboardOpen, snapSizes: [.175, heightFactorForBottomNavScreen], controller: widget.draggableScrollableController, expand: false, builder: (BuildContext context, ScrollController scrollController) { double extentInPercent = (heightFactorForBottomNavScreen - extent) / heightFactorForBottomNavScreen; final ThemeData theme = Theme.of(context); return Theme( data: theme.copyWith( colorScheme: theme.colorScheme .copyWith(secondary: Colors.transparent)), child: SingleChildScrollView( physics: const ClampingScrollPhysics(), controller: scrollController, child: Column( children: [ Container( height: 170 * extentInPercent, color: Colors.white38, child: LayoutBuilder(builder: (_, size) { double width = size.maxWidth / _noOfPages; return Row( mainAxisSize: MainAxisSize.min, children: List.generate(_noOfPages, (index) { double sIndex = widget.selectedIndex - index; bool selected = sIndex.abs() <= 0.5; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () {}, child: Container( color: Colors.transparent, width: width, height: 120, ), ); }), ); }), ), // 将焦点节点传入承载输入框的模块,绑定到TextField的focusNode属性上 NavigateHome(focusNode: _inputFocusNode) ], ), ), ); }, ), )
注意事项
请确认当前页面的Scaffold没有设置
resizeToAvoidBottomInset: false,保持默认值为true即可让页面在键盘弹出时自动调整可视区域,避免输入框被键盘遮挡。如果业务场景必须关闭该属性,需要在计算heightFactorForBottomNavScreen时手动扣除MediaQuery.of(context).viewInsets.bottom对应的键盘高度,保证展开位置计算准确。
如果不需要键盘收起后回到之前的sheet位置,可以删除_lastExtentBeforeKeyboardShow相关逻辑,保持sheet在输入完成后停留在最大展开位置即可,符合多数同类交互的用户预期。
内容的提问来源于stack exchange,提问作者Jagraj Singh
相关产品推荐
相关产品推荐

