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

如何将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:32:06