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

如何实现带Tabs及Tabs上方组件的SliverAppBar(仿Talabat App)

实现Talabat风格带Tabs的折叠SliverAppBar

问题描述

我想要实现一个带有Tabs及Tabs上方组件的SliverAppBar,样式和Talabat App类似。以下是我尝试编写的代码,请问该如何调整实现目标效果?

解决方案

核心调整点

  • 替换CustomScrollView为NestedScrollView,它能更好处理头部折叠与Tab栏固定的联动逻辑
  • 将Tab栏整合到SliverAppBar的bottom属性,并通过TabController绑定内容区域
  • 拆分flexibleSpace为搜索栏、Banner等可折叠组件,保留顶部toolbar的固定显示

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

// 全局常量(根据实际项目调整)
class AppSize {
  static const double s10 = 10;
  static const double s14 = 14;
  static const double s25 = 25;
  static const double s28 = 28;
  static const double s32 = 32;
  static const double s44 = 44;
  static const double s50 = 50;
  static const double s60 = 60;
}

class Palette {
  static const Color kPrimaryColor = Color(0xffFF385C);
  static const Color kWhiteColor = Colors.white;
  static const Color onboardingDescriptionColor = Color(0xff888888);
}

class AppRadius {
  static const BorderRadius bottomRadius = BorderRadius.only(
    bottomLeft: Radius.circular(AppSize.s10),
    bottomRight: Radius.circular(AppSize.s10),
  );
}

class AppAssets {
  static const String menu = 'assets/icons/menu.svg';
  static const String shoppingCart = 'assets/icons/cart.svg';
  static const String search = 'assets/icons/search.svg';
}

double getUIPadding(BuildContext context) {
  return MediaQuery.of(context).size.width * 0.04;
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (context, innerBoxIsScrolled) {
          return [
            SliverOverlapAbsorber(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              sliver: SliverAppBar(
                pinned: true,
                expandedHeight: 400,
                toolbarHeight: AppSize.s60,
                backgroundColor: Palette.kWhiteColor,
                leading: GestureDetector(
                  onTap: () {},
                  child: Center(
                    child: Container(
                      width: AppSize.s50,
                      height: AppSize.s44,
                      decoration: const BoxDecoration(
                        color: Palette.kPrimaryColor,
                        borderRadius: BorderRadius.all(Radius.circular(AppSize.s10)),
                      ),
                      child: Center(
                        child: SvgPicture.asset(
                          AppAssets.menu,
                          height: AppSize.s14,
                        ),
                      ),
                    ),
                  ),
                ),
                title: const Text(
                  "Talabat风格",
                  style: TextStyle(color: Colors.black),
                ),
                actions: [
                  SizedBox(width: AppSize.s10),
                  IconButton(
                    onPressed: () {},
                    splashRadius: AppSize.s28,
                    icon: SvgPicture.asset(
                      AppAssets.shoppingCart,
                      height: AppSize.s32,
                    ),
                  ),
                ],
                flexibleSpace: FlexibleSpaceBar(
                  background: Column(
                    children: [
                      // 展开状态显示的搜索栏
                      Container(
                        margin: EdgeInsets.symmetric(
                          horizontal: getUIPadding(context),
                          vertical: AppSize.s10,
                        ),
                        decoration: BoxDecoration(
                          color: Palette.kWhiteColor,
                          borderRadius: AppRadius.bottomRadius,
                          boxShadow: const [
                            BoxShadow(
                              color: Color.fromARGB(20, 0, 0, 0),
                              blurRadius: 7,
                            )
                          ],
                        ),
                        child: TextFormField(
                          style: const TextStyle(height: 1.2),
                          decoration: InputDecoration(
                            hintText: "عن ماذا تبحث؟",
                            hintStyle: const TextStyle(color: Palette.onboardingDescriptionColor),
                            prefixIcon: Align(
                              widthFactor: 1.0,
                              child: SvgPicture.asset(
                                AppAssets.search,
                                width: AppSize.s25,
                              ),
                            ),
                            isDense: true,
                            border: OutlineInputBorder(
                              borderRadius: AppRadius.bottomRadius,
                              borderSide: BorderSide.none,
                            ),
                          ),
                        ),
                      ),
                      // 折叠区域的Banner组件
                      Expanded(
                        child: Column(
                          children: [
                            Container(height: 100, color: Colors.green, margin: EdgeInsets.all(AppSize.s10)),
                            Container(height: 100, color: Colors.black, margin: EdgeInsets.all(AppSize.s10)),
                            Container(height: 100, color: Colors.black26, margin: EdgeInsets.all(AppSize.s10)),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
                // 固定在顶部的Tab栏
                bottom: TabBar(
                  controller: _tabController,
                  labelColor: Palette.kPrimaryColor,
                  unselectedLabelColor: Colors.grey,
                  indicatorColor: Palette.kPrimaryColor,
                  tabs: const [
                    Tab(text: "餐厅"),
                    Tab(text: "超市"),
                    Tab(text: "更多"),
                  ],
                ),
              ),
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: [
            _buildTabContent("餐厅内容"),
            _buildTabContent("超市内容"),
            _buildTabContent("更多内容"),
          ],
        ),
      ),
    );
  }

  Widget _buildTabContent(String title) {
    return Builder(
      builder: (context) {
        return CustomScrollView(
          slivers: [
            SliverOverlapInjector(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) {
                  return Container(
                    height: 100,
                    color: index % 2 == 0 ? Colors.green : Colors.purple,
                    margin: EdgeInsets.all(AppSize.s10),
                    alignment: Alignment.center,
                    child: Text("$title - 条目 $index", style: const TextStyle(color: Colors.white, fontSize: 16)),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        );
      },
    );
  }
}

关键说明

  1. TabController:通过SingleTickerProviderStateMixin实现,管理Tab的切换逻辑
  2. SliverOverlapAbsorber/Injector:解决NestedScrollView中头部与内容区域的重叠问题
  3. FlexibleSpaceBar:将搜索栏和Banner放在这里,实现滚动折叠效果;Tab栏固定在SliverAppBar底部,滚动时始终保持在顶部
  4. TabBarView:每个Tab对应独立的滚动列表,保持与头部的联动

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:06:29