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

Flutter实现类似iPhone谷歌地图的SliverAppBar(展开状态专属组件)

实现类似谷歌地图的展开/折叠式SliverAppBar

我刚好之前做过类似的需求,核心就是要精准控制组件只在SliverAppBar展开状态下显示。下面给你两种经过验证的可行方案,你可以根据自己的场景选择:

方案一:通过滚动通知监听展开状态

这个方案是通过监听CustomScrollView的滚动事件,计算SliverAppBar的展开比例,从而动态控制组件的显示与隐藏,实现起来非常直观:

class MapLikeAppBar extends StatefulWidget {
  const MapLikeAppBar({super.key});

  @override
  State<MapLikeAppBar> createState() => _MapLikeAppBarState();
}

class _MapLikeAppBarState extends State<MapLikeAppBar> {
  bool _isAppBarExpanded = true;
  final double _expandedHeight = 200; // 自定义展开高度

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<ScrollUpdateNotification>(
        onNotification: (notification) {
          // 计算当前滚动偏移对应的展开比例
          final scrollOffset = notification.metrics.pixels;
          final expansionRatio = (_expandedHeight - scrollOffset) / _expandedHeight;
          
          // 当比例超过0.5时判定为展开状态(阈值可根据需求调整)
          setState(() {
            _isAppBarExpanded = expansionRatio > 0.5;
          });
          return false; // 允许通知继续传播
        },
        child: CustomScrollView(
          slivers: [
            SliverAppBar(
              expandedHeight: _expandedHeight,
              pinned: true, // 保持AppBar始终在顶部
              flexibleSpace: FlexibleSpaceBar(
                background: Stack(
                  fit: StackFit.expand,
                  children: [
                    // 替换成你的地图背景或其他组件
                    Container(color: Colors.blueGrey[100]),
                    // 仅在展开状态显示的组件(比如搜索框)
                    if (_isAppBarExpanded)
                      Positioned(
                        bottom: 20,
                        left: 20,
                        right: 20,
                        child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                          decoration: BoxDecoration(
                            color: Colors.white,
                            borderRadius: BorderRadius.circular(8),
                            boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
                          ),
                          child: const TextField(
                            decoration: InputDecoration(
                              hintText: "搜索地点...",
                              border: InputBorder.none,
                              prefixIcon: Icon(Icons.search),
                            ),
                          ),
                        ),
                      ),
                  ],
                ),
                title: const Text("我的地图"),
              ),
            ),
            // 页面主体内容
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, index) => ListTile(title: Text("列表项 ${index + 1}")),
                childCount: 50,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

方案说明

  • 利用NotificationListener捕获滚动更新事件,计算展开比例来判断AppBar状态
  • 优点:逻辑简单易懂,容易自定义阈值
  • 小缺点:滚动时会频繁触发setState,但对于大多数场景性能影响可以忽略

方案二:用LayoutBuilder实时获取布局约束

这个方案更高效,不需要维护状态,直接通过LayoutBuilder获取FlexibleSpaceBar的实时高度来判断展开状态:

class MapLikeAppBar extends StatelessWidget {
  const MapLikeAppBar({super.key});

  final double _expandedHeight = 200;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            expandedHeight: _expandedHeight,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              background: LayoutBuilder(
                builder: (context, constraints) {
                  // 获取当前FlexibleSpaceBar的可用高度
                  final currentHeight = constraints.maxHeight;
                  // 默认折叠状态的AppBar高度是kToolbarHeight(56),加上20的阈值判断展开
                  final isExpanded = currentHeight > kToolbarHeight + 20;

                  return Stack(
                    fit: StackFit.expand,
                    children: [
                      Container(color: Colors.blueGrey[100]),
                      // 用AnimatedOpacity实现平滑过渡效果
                      AnimatedOpacity(
                        opacity: isExpanded ? 1 : 0,
                        duration: const Duration(milliseconds: 200),
                        child: Positioned(
                          bottom: 20,
                          left: 20,
                          right: 20,
                          child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                            decoration: BoxDecoration(
                              color: Colors.white,
                              borderRadius: BorderRadius.circular(8),
                              boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
                            ),
                            child: const TextField(
                              decoration: InputDecoration(
                                hintText: "搜索地点...",
                                border: InputBorder.none,
                                prefixIcon: Icon(Icons.search),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ],
                  );
                },
              ),
              title: const Text("我的地图"),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => ListTile(title: Text("列表项 ${index + 1}")),
              childCount: 50,
            ),
          ),
        ],
      ),
    );
  }
}

方案说明

  • 利用LayoutBuilder获取FlexibleSpaceBar的实时布局约束,通过高度对比判断展开状态
  • 搭配AnimatedOpacity可以实现组件的平滑淡入淡出,更接近谷歌地图的体验
  • 优点:无状态组件,性能更优,只有布局变化时才会触发重绘

额外优化建议

  • 如果你需要更细腻的过渡效果,可以用AnimatedContainer替代AnimatedOpacity,实现位置或大小的动画
  • 阈值可以根据需求调整,比如想让组件在AppBar展开到70%时显示,就用(_expandedHeight * 0.7)作为判断值
  • 可以把判断逻辑封装成一个工具方法,方便在多个地方复用

内容的提问来源于stack exchange,提问作者Andrei Jiácomo Zuse

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:50:47