Flutter实现带图片AppBar、自定义容器及滚动吸附TabBar问题求助
解决方案:滚动吸附TabBar+自定义展开头部
问题背景
已通过appbar_animated: ^0.0.3实现滚动时顶部图片、餐厅信息容器消失,AppBar切换为普通样式的效果。现需添加TabBar置于这些元素下方,且滚动时TabBar能吸附到AppBar。尝试过NestedScrollView+SliverAppBar方案,但不知如何添加展开状态的背景图,以及放置自定义的restaurantInfoContainer。
实现思路
直接基于NestedScrollView+SliverAppBar完成所有需求:
- 将背景图、餐厅信息容器放入
SliverAppBar的flexibleSpace中,实现滚动时的展开/收起动画 - TabBar绑定到
SliverAppBar的bottom属性,滚动时自动吸附到顶部 - 通过
pinned: true保持AppBar和TabBar在滚动到顶部时固定显示
完整代码实现
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'package:flutter/material.dart'; class TalabadScreenWithTab extends StatefulWidget { const TalabadScreenWithTab({super.key}); @override State<TalabadScreenWithTab> createState() => _TalabadScreenWithTabState(); } class _TalabadScreenWithTabState extends State<TalabadScreenWithTab> with SingleTickerProviderStateMixin { final _tabs = List.generate(10, (index) => 'Tab#${index + 1}'); late final TabController _tabCont; @override void initState() { _tabCont = TabController(length: 10, vsync: this); super.initState(); } @override Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final expandedHeaderHeight = screenHeight * 0.35; return Scaffold( backgroundColor: Colors.grey[200], body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) { return [ SliverAppBar( backgroundColor: innerBoxIsScrolled ? Colors.white : Colors.transparent, scrolledUnderElevation: innerBoxIsScrolled ? 4 : 0, elevation: 0, leading: CircleAvatar( backgroundColor: Colors.white, child: IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.arrow_back, color: Colors.black87, size: 30), ), ), actions: [ CircleAvatar( radius: 28, backgroundColor: Colors.white, child: IconButton( padding: EdgeInsets.all(0), icon: Icon(Icons.ios_share_rounded, color: Colors.black87, size: 30), onPressed: () {}, ), ), SizedBox(width: 10), CircleAvatar( radius: 28, backgroundColor: Colors.white, child: IconButton( padding: EdgeInsets.all(0), icon: Icon(Icons.search, color: Colors.black87, size: 30), onPressed: () {}, ), ), SizedBox(width: 10), ], expandedHeight: expandedHeaderHeight, pinned: true, floating: false, centerTitle: true, // 自定义展开头部内容 flexibleSpace: FlexibleSpaceBar( background: Stack( fit: StackFit.expand, children: [ // 顶部背景图 Image.asset( "img/cover.jpg", fit: BoxFit.cover, ), // 白色底部容器(覆盖背景图下半部分) Positioned( bottom: 0, left: 0, right: 0, child: Container( height: screenHeight * 0.1, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), ), ), // 餐厅信息容器(悬浮在白色容器上方) Positioned( bottom: screenHeight * 0.05, left: 10, right: 10, child: restaurantInfoContainer(), ), ], ), ), // TabBar部分 bottom: TabBar( controller: _tabCont, isScrollable: true, indicatorColor: Colors.red, labelColor: Colors.black87, unselectedLabelColor: Colors.grey, tabs: _tabs.map((label) => Tab(text: label)).toList(), ), ), ]; }, // TabBarView内容 body: TabBarView( controller: _tabCont, children: _tabs.map((label) { return ListView.builder( padding: EdgeInsets.zero, itemCount: 50, itemBuilder: (context, index) { return ListTile( title: Text('$label - 列表项 $index'), ); }, ); }).toList(), ), ), ); } // 自定义餐厅信息容器(替换成你自己的实现) Widget restaurantInfoContainer() { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('餐厅名称', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), SizedBox(height: 8), Row( children: [ Icon(Icons.star, color: Colors.amber, size: 16), SizedBox(width: 4), Text('4.8 (1200+ 评价)'), SizedBox(width: 16), Text('·'), SizedBox(width: 16), Text('快餐'), ], ), ], ), ); } }
关键说明
- FlexibleSpaceBar:承载背景图、白色容器和餐厅信息,实现滚动时的收起动画
- pinned: true:确保SliverAppBar和TabBar在滚动到顶部时保持固定
- innerBoxIsScrolled:判断滚动状态,动态切换AppBar的背景色和阴影
- TabController联动:保证TabBar和TabBarView的选中状态同步
内容的提问来源于stack exchange,提问作者Ahmed Zaman
相关产品推荐
相关产品推荐

