Flutter showModalBottomSheet结合ListView.builder动态高度不生效问题
Flutter 自适应高度BottomSheet实现方案
问题背景
- 实现目标:开发可根据内容尺寸自适应高度的BottomSheet,组件嵌套结构为
Container -> Column -> ListView.builder - 预期交互:
- 列表数据少于10条时,BottomSheet完全适配内容高度
- 列表数据多于10条时,BottomSheet展示固定默认高度,超出部分内容支持滚动查看
- 已尝试但未生效的方案:
- 为
showModalBottomSheet配置isScrollControlled: true属性 - 为
ListView配置shrinkWrap: true属性 - 嵌套
Wrap组件适配布局
注:不使用ListView仅使用Wrap组件时,自适应高度功能可正常运行
- 为
问题根因
原有代码存在4个核心布局错误:
- 在
SingleChildScrollView内部嵌套Expanded:Expanded会强制子组件撑满剩余可用空间,与滚动视图自适应内容的布局逻辑冲突,会触发布局溢出异常 Column未设置mainAxisSize属性:Column默认mainAxisSize: MainAxisSize.max,会强制撑满父组件最大可用高度,无法跟随内容收缩- 未给长列表场景设置高度约束:列表项过多时无高度上限,要么撑满全屏要么出现布局溢出,也无法触发ListView自身滚动
- 外层多余的
SingleChildScrollView会和内部ListView产生滚动冲突,干扰高度计算
修复步骤
- 开启
showModalBottomSheet的isScrollControlled: true配置,授予弹窗完整高度访问权限 - 移除外层多余的
SingleChildScrollView和错误的Expanded嵌套,Column直接作为Padding的子组件 - 给
Column设置mainAxisSize: MainAxisSize.min,让列高度跟随内容自动收缩 - 增加列表长度判断逻辑:
- 列表项≤10条时:给
ListView.builder配置shrinkWrap: true+physics: const NeverScrollableScrollPhysics(),让列表高度完全适配内容,禁止列表自身滚动,整体高度由内容决定 - 列表项>10条时:用
ConstrainedBox包裹列表,设置最大高度阈值(可按需调整,示例为屏幕高度的60%),此时ListView自动接管滚动,展示超出部分内容
- 列表项≤10条时:给
- 给ListView配置
padding: EdgeInsets.zero,移除ListView默认内边距,避免高度计算偏差
修正后完整代码
class BottomStringlistController { var selectedValue = ""; void showSheet(BuildContext context, List<String> values, String currentTitle, {required Function(String value) onCompletion}){ var bottomSheet = showModalBottomSheet<void>( isScrollControlled: true, backgroundColor: Colors.transparent, context: context, builder: (BuildContext context) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all( Radius.circular(15.0), ), ), child: Padding( padding: const EdgeInsets.all(15.0), child: Column( // 核心配置:让Column高度跟随内容收缩 mainAxisSize: MainAxisSize.min, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( currentTitle.toUpperCase(), textAlign: TextAlign.center, style: TextStyle( fontSize: getProportionateScreenWidth(14), fontWeight: FontWeight.bold, color: kPrimaryColor, ), ), const Spacer(), IconButton( onPressed:(){ Navigator.pop(context); }, icon: const Icon(Icons.close,color: kPrimaryColor,), ), ], ), const Divider(color: kPrimaryColor,), SizedBox(height: getProportionateScreenWidth(15),), // 根据列表长度切换布局模式 values.length <= 10 ? ListView.builder( shrinkWrap: true, // 短列表禁止自身滚动,高度完全适配内容 physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: values.length, itemBuilder: (context, index){ return GestureDetector( onTap: (){ selectedValue = values[index]; Navigator.pop(context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(values[index].toUpperCase(), textAlign: TextAlign.start, style: TextStyle( fontSize: getProportionateScreenWidth(12), fontWeight: FontWeight.normal, color: Colors.black ), ), const SizedBox(height: 10), ], ), ); }, ) : ConstrainedBox( // 长列表设置最大高度,超出部分滚动 constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.6, ), child: ListView.builder( padding: EdgeInsets.zero, itemCount: values.length, itemBuilder: (context, index){ return GestureDetector( onTap: (){ selectedValue = values[index]; Navigator.pop(context); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(values[index].toUpperCase(), textAlign: TextAlign.start, style: TextStyle( fontSize: getProportionateScreenWidth(12), fontWeight: FontWeight.normal, color: Colors.black ), ), const SizedBox(height: 10), ], ), ); }, ), ), ], ), ), ); }, ).whenComplete(() { onCompletion(selectedValue); }); } }
提示:长列表的最大高度可根据业务需求自行调整maxHeight的数值,不需要固定为屏幕高度的60%
内容的提问来源于stack exchange,提问作者karthikeyan
相关产品推荐
相关产品推荐

