如何在Flutter中实现iPhone Safari底部可缩放浮动搜索栏UI
实现iPhone Safari底部可缩放搜索栏(Flutter)
需求回顾
- 底部搜索栏随滚动实时缩放
- 向下滚动时不遮挡内容
- 随时可查看/操作搜索内容
核心实现方案
通过NotificationListener监听滚动事件,结合AnimatedContainer实现搜索栏的平滑缩放,用Stack确保搜索栏固定在底部且不遮挡滚动内容。
具体实现步骤
- 监听滚动状态:用
NotificationListener<ScrollNotification>包裹滚动组件,捕获滚动偏移量和方向 - 动态计算缩放比例:根据滚动方向/偏移量调整缩放系数,限制在合理范围(比如0.6~1.0)
- 平滑缩放过渡:用
AnimatedContainer根据缩放系数动态调整搜索栏的尺寸、位置,实现自然动画 - 布局层级:用
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
相关产品推荐
相关产品推荐

