Flutter实现带Cupertino搜索框的可展开iOS风格导航栏
需求与当前实现
我正在用Flutter开发iOS应用,需要一个符合以下要求的可展开导航栏:
- 展开状态:左侧显示大标题,下方带搜索框
- 收起状态:标题居中
- 滚动行为遵循iOS默认逻辑:向上滚动时先收起搜索框,再折叠导航栏;向下滚动时先展开导航栏,再显示搜索框
目前用CupertinoSliverNavigationBar实现了基础的展开/收起导航栏效果,但搜索框是单独的SliverToBoxAdapter,滚动时无法实现上述联动逻辑。当前代码如下:
CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: _scrollController, slivers: <Widget> [ const CupertinoSliverNavigationBar( largeTitle: Text('Products'), stretch: true, border: Border(), trailing: Icon(CupertinoIcons.add,color: CupertinoColors.systemBlue,size: 24,), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: CupertinoSearchTextField( controller: _controller, onSubmitted: (String value) {}, ), ), ), SliverFillRemaining( child: _controller.text.isNotEmpty? paymentList(state.productDataSearch!,state): paymentList(state.productData!,state), ), ], ),
已实现效果:导航栏可展开/收起,但搜索框和导航栏滚动无联动,向上滚动时两者同时收起;向下滚动时同时展开。
目标效果:iOS原生滚动联动逻辑,先处理搜索框的显示/隐藏,再处理导航栏的展开/折叠。
解决方案
要实现这种iOS原生的滚动联动逻辑,需要把搜索框整合到导航栏的可展开区域,并通过监听滚动偏移控制其显示状态,配合CupertinoSliverNavigationBar的原生行为。具体实现代码如下:
class _ProductPageState extends State<ProductPage> { final ScrollController _scrollController = ScrollController(); final TextEditingController _controller = TextEditingController(); bool _isNavBarExpanded = true; final double _searchBarMaxHeight = 50; double _searchBarHeight = 50; @override void initState() { super.initState(); _scrollController.addListener(_handleScroll); } @override void dispose() { _scrollController.removeListener(_handleScroll); _scrollController.dispose(); _controller.dispose(); super.dispose(); } void _handleScroll() { final double navBarCollapseThreshold = MediaQuery.of(context).padding.top + 44; final double scrollOffset = _scrollController.offset; setState(() { _isNavBarExpanded = scrollOffset < navBarCollapseThreshold; if (scrollOffset <= 0) { _searchBarHeight = _searchBarMaxHeight; } else if (scrollOffset < navBarCollapseThreshold) { _searchBarHeight = _searchBarMaxHeight - (scrollOffset / navBarCollapseThreshold) * _searchBarMaxHeight; } else { _searchBarHeight = 0; } }); } @override Widget build(BuildContext context) { return CupertinoPageScaffold( child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: _scrollController, slivers: <Widget>[ CupertinoSliverNavigationBar( largeTitle: _isNavBarExpanded ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Products'), SizedBox( height: _searchBarHeight, child: Padding( padding: const EdgeInsets.only(top: 8), child: CupertinoSearchTextField( controller: _controller, onSubmitted: (String value) {}, ), ), ), ], ) : null, middle: const Text('Products'), stretch: true, border: const Border(), trailing: const Icon(CupertinoIcons.add, color: CupertinoColors.systemBlue, size: 24), ), SliverFillRemaining( child: _controller.text.isNotEmpty ? paymentList(state.productDataSearch!, state) : paymentList(state.productData!, state), ), ], ), ); } }
关键逻辑说明
- 滚动监听:通过
ScrollController捕获滚动偏移,计算导航栏展开状态和搜索框高度 - 动态搜索框:将搜索框作为
largeTitle的一部分,根据滚动偏移调整高度,实现“先收起搜索框再折叠导航栏”的顺序 - 导航栏状态切换:导航栏收起时,
middle属性显示居中标题;展开时,largeTitle显示左侧大标题和动态高度的搜索框
内容的提问来源于stack exchange,提问作者Umar Ghaffar
相关产品推荐
相关产品推荐

