Flutter中Wrap组件内Chip超指定行数时如何添加显示更多按钮
Wrap内Chip超3行显示「查看更多」实现方法
需求说明
后端动态返回数据列表,当前使用Wrap组件嵌套Chip渲染条目,需要实现:Chip展示行数超过3行时,隐藏超出部分,同时展示See More按钮,点击可查看全部内容。
原有实现代码:
Wrap( children: [ for (var shop in _viewModel.shops.types) Padding( padding: const EdgeInsets.symmetric( horizontal: 4.0, ), child: Chip( label: Text( shop.shopName, ), labelStyle: Theme.of(context).textTheme.headline6!.copyWith( color: AppColors.darkerGreyColor, ), backgroundColor: AppColors.textLightColor, shape: const StadiumBorder( side: BorderSide( color: Color(0xFFBDBDBD), ), ), padding: const EdgeInsets.all( AppSizes.p4, ), ), ), ], );
原有效果:
可落地实现方式
原生Wrap没有内置最大行数+尾缀展开按钮的能力,选下面两种方式之一即可:
方式一:使用现成扩展包(开发成本最低)
直接用专门适配该场景的wrap_widget包,不需要手动计算布局,稳定性有保障:
- 在
pubspec.yaml中添加依赖,版本取pub平台最新稳定版即可:
dependencies: wrap_widget: ^1.0.4
- 替换原有
Wrap代码,直接配置参数即可:
// 加状态变量控制展开状态 bool _isExpanded = false; // 组件部分 WrapWidget( maxLine: _isExpanded ? null : 3, // 收起时最多显示3行,展开时不限制 spacing: 8, // 横向子组件间距 runSpacing: 4, // 纵向行间距 children: [ for (var shop in _viewModel.shops.types) Chip( label: Text(shop.shopName), labelStyle: Theme.of(context).textTheme.headline6!.copyWith( color: AppColors.darkerGreyColor, ), backgroundColor: AppColors.textLightColor, shape: const StadiumBorder( side: BorderSide( color: Color(0xFFBDBDBD), ), ), padding: const EdgeInsets.all(AppSizes.p4), ), // 展开/收起按钮 ActionChip( label: Text(_isExpanded ? '收起' : 'See More'), onPressed: () { setState(() { _isExpanded = !_isExpanded; }); }, ) ], )
组件会自动计算3行内能放下的Chip数量,预留按钮位置,超出部分自动隐藏,不需要额外写布局计算逻辑。
方式二:不引入第三方依赖,自行计算布局
如果项目不允许引入额外依赖,可以通过布局监听手动计算可见Chip数量:
- 先定义状态变量:
bool _isExpanded = false; int _visibleChipCount = 0; // 根据自己项目的Chip样式调整单行高度、行间距 final double _singleLineHeight = 40; final double _runSpacing = 8;
- 通过
LayoutBuilder拿到容器可用宽度,首帧渲染完成后计算3行高度范围内可容纳的Chip数量,注意要预留出See More按钮的宽度:
LayoutBuilder( builder: (context, constraints) { // 首帧布局完成后计算可见数量 WidgetsBinding.instance.addPostFrameCallback((_) { if (_isExpanded || _viewModel.shops.types.isEmpty) return; final maxAllowHeight = 3 * _singleLineHeight + 2 * _runSpacing; // 遍历通过GlobalKey拿到每个Chip的RenderBox,累加计算位置 // 统计总高度不超过maxAllowHeight时最多可显示的Chip数,赋值给_visibleChipCount setState(() {}); }); final displayShops = _isExpanded ? _viewModel.shops.types : _viewModel.shops.types.take(_visibleChipCount).toList(); return Wrap( spacing: 8, runSpacing: _runSpacing, children: [ for (var shop in displayShops) Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Chip( label: Text(shop.shopName), labelStyle: Theme.of(context).textTheme.headline6!.copyWith( color: AppColors.darkerGreyColor, ), backgroundColor: AppColors.textLightColor, shape: const StadiumBorder( side: BorderSide( color: Color(0xFFBDBDBD), ), ), padding: const EdgeInsets.all(AppSizes.p4), ), ), if (!_isExpanded && _visibleChipCount < _viewModel.shops.types.length) ActionChip( label: const Text('See More'), onPressed: () => setState(() => _isExpanded = true), ) ], ); }, )
该方案需要额外处理屏幕旋转、系统字体缩放、数据更新场景下的重新计算,逻辑相对繁琐,优先推荐第一种方式。
内容的提问来源于stack exchange,提问作者Hitesh Garg
相关产品推荐
相关产品推荐

