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

如何在Flutter中实现iPhone Safari底部可缩放浮动搜索栏UI

实现iPhone Safari底部可缩放搜索栏(Flutter)

需求回顾

  • 底部搜索栏随滚动实时缩放
  • 向下滚动时不遮挡内容
  • 随时可查看/操作搜索内容

核心实现方案

通过NotificationListener监听滚动事件,结合AnimatedContainer实现搜索栏的平滑缩放,用Stack确保搜索栏固定在底部且不遮挡滚动内容。

具体实现步骤

  1. 监听滚动状态:用NotificationListener<ScrollNotification>包裹滚动组件,捕获滚动偏移量和方向
  2. 动态计算缩放比例:根据滚动方向/偏移量调整缩放系数,限制在合理范围(比如0.6~1.0)
  3. 平滑缩放过渡:用AnimatedContainer根据缩放系数动态调整搜索栏的尺寸、位置,实现自然动画
  4. 布局层级:用Stack将滚动内容放在底层,搜索栏放在顶层底部,避免遮挡

完整代码示例

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

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

  @override
  State<SafariBottomSearchBar> createState() => _SafariBottomSearchBarState();
}

class _SafariBottomSearchBarState extends State<SafariBottomSearchBar> {
  // 缩放系数:1.0为正常大小,0.6为最小缩放比例
  double _scale = 1.0;
  // 记录上一次滚动偏移量,用于判断滚动方向
  double _lastScrollOffset = 0.0;

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      body: Stack(
        children: [
          // 滚动内容区域
          NotificationListener<ScrollNotification>(
            onNotification: (notification) {
              if (notification is ScrollUpdateNotification) {
                final currentOffset = notification.metrics.pixels;
                final scrollDelta = currentOffset - _lastScrollOffset;

                setState(() {
                  // 向下滚动(delta为正)缩小搜索栏,向上滚动(delta为负)恢复
                  _scale = clampDouble(
                    _scale - scrollDelta * 0.001,
                    0.6,
                    1.0,
                  );
                  _lastScrollOffset = currentOffset;
                });
              }
              return false; // 允许事件继续传递
            },
            child: ListView.builder(
              padding: EdgeInsets.only(bottom: 80), // 给底部搜索栏留空间
              itemCount: 50,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text('列表项 $index'),
                  contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                );
              },
            ),
          ),
          // 底部可缩放搜索栏
          Positioned(
            bottom: 20,
            // 居中定位:左右留白 = (屏幕宽度 - 缩放后的宽度)/2
            left: (screenWidth - screenWidth * _scale) / 2,
            child: AnimatedContainer(
              duration: Duration(milliseconds: 80),
              width: screenWidth * _scale,
              height: _scale == 1.0 ? 52 : 42,
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(_scale == 1.0 ? 26 : 21),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.1),
                    blurRadius: 6,
                    offset: Offset(0, 2),
                  )
                ],
              ),
              child: TextField(
                decoration: InputDecoration(
                  hintText: '搜索',
                  border: InputBorder.none,
                  prefixIcon: Icon(Icons.search, color: Colors.grey[600]),
                  contentPadding: EdgeInsets.symmetric(
                    horizontal: 16,
                    vertical: _scale == 1.0 ? 14 : 10,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

代码说明

  • NotificationListener:捕获滚动更新事件,通过计算滚动delta值动态调整缩放系数,实现实时缩放
  • clampDouble:限制缩放范围在0.6到1.0之间,避免过度缩放
  • AnimatedContainer:自动处理尺寸、圆角的平滑过渡动画,无需手动写动画控制器
  • Positioned:通过动态计算left值,确保搜索栏始终居中显示,缩放时不会偏移

优化建议

  • 可根据需求调整缩放的灵敏度(scrollDelta * 0.001中的系数)
  • 可以添加搜索栏的透明度变化,增强视觉效果
  • 如果使用CustomScrollView,只需将ListView.builder替换为对应的slivers即可

内容的提问来源于stack exchange,提问作者decaffeineOnly

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:40:22