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

Flutter使用scrollable_list_tabview实现分类菜单及动态高度适配求助

解决方案

1. 食品数据按分类分组实现

先把接口返回的JSON数据转换为Dart模型类(Food类和Category类),再对食品数据按category_id做分组,同时去重得到所有唯一分类作为tab的数据源:

// 提前定义的分类模型
class Category {
  final int id;
  final String name;
  Category({required this.id, required this.name});
}

// 提前定义的食品模型
class Food {
  final int id;
  final String name;
  final double price;
  final int categoryId;
  final Category category;
  Food({required this.id, required this.name, required this.price, required this.categoryId, required this.category});
}

// 页面内定义的变量
// 原接口返回的所有食品数组,替换为你实际的数据源
List<Food> _allFoods = [];
// 分类映射:key为category_id,value为对应分类下的所有食品列表
Map<int, List<Food>> _foodsByCategory = {};
// 去重后的分类列表,用于生成tab
List<Category> _categories = [];

// 数据分组方法,在接口返回食品数据后调用
void _groupFoodData() {
  _foodsByCategory.clear();
  _categories.clear();
  Set<int> addedCategoryIds = {};
  for (var food in _allFoods) {
    if (!_foodsByCategory.containsKey(food.categoryId)) {
      _foodsByCategory[food.categoryId] = [];
      // 分类去重
      if (!addedCategoryIds.contains(food.category.id)) {
        addedCategoryIds.add(food.category.id);
        _categories.add(food.category);
      }
    }
    _foodsByCategory[food.categoryId]!.add(food);
  }
  // 分组完成后计算自适应高度
  _calculateExpandedHeight();
}

2. SliverAppBar高度自适应实现

因为餐饮菜单的列表项布局统一高度固定,直接累加所有列表项高度加上tab栏高度即可得到动态的expandedHeight:

// 页面内变量定义
// 你设置的tab栏高度
final double _tabHeight = 50;
// 单个食品列表项的高度,ListTile默认高度为56,自定义布局请替换为实际高度
final double _foodItemHeight = 56;
// 计算得到的自适应展开高度
double _expandedHeight = 0;

// 高度计算方法
void _calculateExpandedHeight() {
  double totalListHeight = 0;
  // 累加所有分类下所有食品项的总高度
  for (var foodList in _foodsByCategory.values) {
    totalListHeight += foodList.length * _foodItemHeight;
  }
  // 总高度 = tab栏高度 + 所有食品列表总高度
  _expandedHeight = _tabHeight + totalListHeight;
  if(mounted) setState(() {});
}

修改后的SliverAppBar代码,动态生成tabs并使用自适应高度:

SliverAppBar(
  backgroundColor: Colors.white,
  automaticallyImplyLeading: false,
  elevation: 0.0,
  // 替换为计算得到的动态高度
  expandedHeight: _expandedHeight,
  flexibleSpace: ScrollableListTabView(
    tabHeight: _tabHeight,
    bodyAnimationDuration: const Duration(milliseconds: 150),
    tabAnimationCurve: Curves.easeOut,
    tabAnimationDuration: const Duration(milliseconds: 200),
    // 动态生成所有分类对应的tab
    tabs: _categories.map((category) {
      List<Food> currentCategoryFoods = _foodsByCategory[category.id] ?? [];
      return ScrollableListTab(
        tab: ListTab(
          label: Text(category.name),
          icon: const Icon(Icons.fastfood),
          showIconOnList: false,
        ),
        body: ListView.builder(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          itemCount: currentCategoryFoods.length,
          itemBuilder: (context, index) {
            Food food = currentCategoryFoods[index];
            return ListTile(
              leading: Container(
                height: 40,
                width: 40,
                decoration: const BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.grey,
                ),
                alignment: Alignment.center,
                child: Text(food.id.toString()),
              ),
              title: Text(food.name),
              subtitle: Text('¥${food.price.toStringAsFixed(2)}'),
            );
          },
        ),
      );
    }).toList(),
  ),
),

注意事项

  • 如果你自定义了食品列表项的布局,请把_foodItemHeight替换为你实际的每行高度值,保证高度计算准确
  • 如果列表项高度不固定,可使用尺寸测量类第三方库动态测量每个item的高度再累加,常规餐饮菜单用固定高度计算的方案即可满足需求
  • 请在接口加载完食品数据后再调用_groupFoodData()方法,避免空数据导致高度计算错误

内容的提问来源于stack exchange,提问作者Ismail Muhammad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 06:54:03