如何实现带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, ), ), ], ); }, ); } }
关键说明
- TabController:通过
SingleTickerProviderStateMixin实现,管理Tab的切换逻辑 - SliverOverlapAbsorber/Injector:解决NestedScrollView中头部与内容区域的重叠问题
- FlexibleSpaceBar:将搜索栏和Banner放在这里,实现滚动折叠效果;Tab栏固定在SliverAppBar底部,滚动时始终保持在顶部
- TabBarView:每个Tab对应独立的滚动列表,保持与头部的联动
内容的提问来源于stack exchange,提问作者Muhammad Kamel
相关产品推荐
相关产品推荐

