如何将SliverAppBar固定在状态栏下方?
解决方案
核心思路是通过自定义SliverPersistentHeaderDelegate实现动态适配安全区域:仅当搜索栏固定到屏幕顶部时,才添加状态栏高度的内边距,避免和第一个SliverAppBar之间出现多余空隙。
修改后的完整代码
class MyPage extends StatefulWidget { const MyPage({super.key}); @override State<MyPage> createState() => _MyPageState(); } class _MyPageState extends State<MyPage> { late ScrollController _scrollController; @override void initState() { _scrollController = ScrollController(); super.initState(); } @override Widget build(BuildContext context) { final statusBarHeight = MediaQuery.of(context).padding.top; return Scaffold( body: PrimaryScrollController( controller: _scrollController, child: CustomScrollView( slivers: <Widget>[ SliverAppBar( pinned: false, expandedHeight: 200, flexibleSpace: FlexibleSpaceBar( background: Image.network( 'https://picsum.photos/250?image=9', fit: BoxFit.cover, ), ), ), SliverPersistentHeader( pinned: true, delegate: SearchHeaderDelegate( statusBarHeight: statusBarHeight, child: const SearchBar(), ), ), SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return ListTile( title: Text('Item $index'), ); }, childCount: 20, ), ), ], ), ), ); } } class SearchHeaderDelegate extends SliverPersistentHeaderDelegate { final double statusBarHeight; final Widget child; SearchHeaderDelegate({ required this.statusBarHeight, required this.child, }); // 搜索栏本身的高度(可根据实际SearchBar调整) final _searchBarHeight = 56.0; @override double get minExtent => _searchBarHeight; @override double get maxExtent => _searchBarHeight; @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { // 仅当头部完全固定时,添加状态栏高度的内边距 final topPadding = shrinkOffset >= maxExtent - minExtent ? statusBarHeight : 0.0; return Container( color: Theme.of(context).scaffoldBackgroundColor, padding: EdgeInsets.only(top: topPadding), child: child, ); } @override bool shouldRebuild(covariant SearchHeaderDelegate oldDelegate) { return oldDelegate.statusBarHeight != statusBarHeight || oldDelegate.child != child; } }
关键说明
- 动态内边距:通过
shrinkOffset判断搜索栏是否固定到顶部,仅此时添加状态栏高度的内边距,避免和第一个SliverAppBar之间产生空隙。 - 自定义PersistentHeader:替代原固定SliverAppBar,更灵活控制头部布局逻辑。
- 背景色统一:给Container设置与页面一致的背景色,防止滚动时搜索栏下方露出空白。
替代方案(使用原生SliverAppBar)
若坚持使用SliverAppBar,可通过监听滚动控制器动态调整内边距:
// 在State类中添加滚动监听 @override void initState() { super.initState(); _scrollController.addListener(_onScroll); } @override void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); super.dispose(); } bool _isSearchBarPinned = false; void _onScroll() { // 滚动距离超过第一个SliverAppBar高度时,标记搜索栏已固定 setState(() { _isSearchBarPinned = _scrollController.offset >= 200; }); } // 第二个SliverAppBar修改为: SliverAppBar( pinned: true, toolbarHeight: 56 + (_isSearchBarPinned ? MediaQuery.of(context).padding.top : 0), flexibleSpace: Padding( padding: EdgeInsets.only(top: _isSearchBarPinned ? MediaQuery.of(context).padding.top : 12.0), child: const SearchBar(), ), )
内容的提问来源于stack exchange,提问作者RonanV
相关产品推荐
相关产品推荐

