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

Flutter中如何跟随用户滚动实现类似iOS设置的搜索栏动画

实现iOS设置风格的滚动显示/隐藏搜索栏效果

核心思路

放弃Sliver的默认预设行为,改用ScrollController监听滚动方向,结合AnimationController实现精准的平滑显示/隐藏动画:

  • 初始状态下搜索栏完全隐藏
  • 监听滚动偏移量变化,通过对比前后偏移值判断滚动方向
  • 向上滚动时触发动画隐藏搜索栏;仅在列表顶部向下滚动时触发动画显示搜索栏

完整代码实现

import 'package:flutter/material.dart';

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

  @override
  State<ScrollSearchBar> createState() => _ScrollSearchBarState();
}

class _ScrollSearchBarState extends State<ScrollSearchBar> with SingleTickerProviderStateMixin {
  final ScrollController _scrollController = ScrollController();
  late AnimationController _animationController;
  double _previousOffset = 0;
  // 搜索栏固定高度,可根据需求调整
  static const _searchBarHeight = 56.0;

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器,时长贴近iOS原生动画速度
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
      value: 0, // 初始状态为隐藏
    );
    // 添加滚动监听
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    final currentOffset = _scrollController.offset;
    // 判断滚动方向
    if (currentOffset > _previousOffset) {
      // 向上滚动:若搜索栏未在隐藏状态,触发反向动画
      if (_animationController.status != AnimationStatus.reverse) {
        _animationController.reverse();
      }
    } else {
      // 向下滚动:仅当列表处于顶部时,触发正向动画显示搜索栏
      if (currentOffset <= 0 && _animationController.status != AnimationStatus.forward) {
        _animationController.forward();
      }
    }
    _previousOffset = currentOffset;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 主列表
          ListView.builder(
            controller: _scrollController,
            // 根据动画值动态调整列表顶部padding,避免被搜索栏遮挡
            padding: EdgeInsets.only(top: _animationController.value * _searchBarHeight),
            itemCount: 50,
            itemBuilder: (context, index) {
              return ListTile(title: Text('设置项 $index'));
            },
          ),
          // 动态显示的搜索栏
          AnimatedBuilder(
            animation: _animationController,
            builder: (context, child) {
              return Transform.translate(
                // 通过Y轴偏移控制显示:值为0时完全显示,-56时完全隐藏
                offset: Offset(0, -_searchBarHeight * (1 - _animationController.value)),
                child: Container(
                  height: _searchBarHeight,
                  color: Theme.of(context).scaffoldBackgroundColor,
                  child: const TextField(
                    decoration: InputDecoration(
                      hintText: '搜索设置',
                      prefixIcon: Icon(Icons.search),
                      border: InputBorder.none,
                      contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
                    ),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    _animationController.dispose();
    super.dispose();
  }
}

关键细节说明

  1. 滚动方向判断:通过对比当前偏移量与上一次偏移量,精准区分向上/向下滚动动作
  2. 触发条件限制:仅当列表处于顶部(currentOffset <= 0)时,向下滚动才显示搜索栏,避免中间滚动误触发
  3. 动画流畅性:使用Transform.translate实现偏移动画,比修改容器高度更贴近iOS原生的平滑效果;200ms的动画时长匹配系统动画速度
  4. 列表适配:列表顶部padding随动画值动态调整,确保搜索栏显示时不会遮挡列表内容

为什么Slivers不适用?

SliverAppBar的floating/pinned属性是预设行为,无法实现「仅在顶部向下滚动显示,任意向上滚动隐藏」的自定义逻辑,因此需要手动监听滚动事件来实现完全匹配需求的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 05:36:24