You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,
          ),
        ),
      ),
  ],
);

原有效果:
Chip列表渲染效果


可落地实现方式

原生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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 16:39:35