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

Flutter实现滚动时隐藏AppBar与底部导航栏的方法

Flutter滚动时自动隐藏AppBar和底部导航栏的实现方案

问题描述

我正在开发一款同时包含AppBar和底部导航栏的Flutter应用,希望实现用户滚动时自动隐藏这两个组件:向上滚动列表时,AppBar滑出顶部视野、底部导航栏滑出底部视野;向下滚动回顶部时,两者重新显示。

当前项目包含两个页面:

  • 导航栏页面:编写了底部导航栏和AppBar的代码,其body为用户展示的UI
  • ListView页面:包含数据列表的UI

期望效果:
滚动时隐藏导航栏效果

现有代码

导航栏页面(MyNavBarr)

class MyNavBarr extends StatefulWidget {
  const MyNavBarr({super.key});
  @override
  State<MyNavBarr> createState() => _MyNavBarrState();
}

class _MyNavBarrState extends State<MyNavBarr> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    LiistView(),
    LiistView(),
    LiistView(),
    LiistView(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AppBar"),
        centerTitle: true,
      ),
      body: _widgetOptions.elementAt(_selectedIndex),
      bottomNavigationBar: SizedBox(
        height: 65.0,
        child: BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(IconlyLight.image),
              label: '',
            ),
            BottomNavigationBarItem(
              icon: Icon(IconlyLight.video),
              label: '',
            ),
            BottomNavigationBarItem(
              icon: Icons.library_music_outlined,
              label: '',
            ),
            BottomNavigationBarItem(
              icon: Icon(IconlyLight.profile),
              label: '',
            ),
          ],
          currentIndex: _selectedIndex,
          showSelectedLabels: true,
          showUnselectedLabels: false,
          selectedFontSize: 0.0,
          type: BottomNavigationBarType.fixed,
          onTap: _onItemTapped,
        ),
      ),
    );
  }
}

ListView页面(LiistView)

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

  @override
  State<LiistView> createState() => _LiistViewState();
}

class _LiistViewState extends State<LiistView> {
  final controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: 80,
        itemBuilder: (context, index){
          return const ListTile(
            title: Text("Abcd"),
          );
        },
      ),
    );
  }
}

解决方案代码

导航栏页面(修改后)

class MyNavBarr extends StatefulWidget {
  const MyNavBarr({super.key});
  @override
  State<MyNavBarr> createState() => _MyNavBarrState();
}

class _MyNavBarrState extends State<MyNavBarr> {
  int _selectedIndex = 0;
  bool _isVisible = true;
  late final List<ScrollController> _scrollControllers;

  @override
  void initState() {
    super.initState();
    // 为每个页面创建独立的滚动控制器并添加监听
    _scrollControllers = List.generate(4, (index) {
      final controller = ScrollController();
      controller.addListener(_handleScroll);
      return controller;
    });
  }

  @override
  void dispose() {
    // 销毁所有滚动控制器,避免内存泄漏
    for (var controller in _scrollControllers) {
      controller.dispose();
    }
    super.dispose();
  }

  void _handleScroll() {
    final currentController = _scrollControllers[_selectedIndex];
    // 根据滚动方向切换导航栏显示状态
    if (currentController.position.userScrollDirection == ScrollDirection.reverse) {
      if (_isVisible) setState(() => _isVisible = false);
    } else if (currentController.position.userScrollDirection == ScrollDirection.forward) {
      if (!_isVisible) setState(() => _isVisible = true);
    }
  }

  void _onItemTapped(int index) {
    setState(() => _selectedIndex = index);
  }

  @override
  Widget build(BuildContext context) {
    final appBarHeight = AppBar().preferredSize.height;
    const bottomNavHeight = 65.0;

    return Scaffold(
      body: Stack(
        children: [
          // 内容区域:根据导航栏状态调整上下内边距,避免内容被遮挡
          Padding(
            padding: EdgeInsets.only(
              top: _isVisible ? appBarHeight : 0,
              bottom: _isVisible ? bottomNavHeight : 0,
            ),
            child: IndexedStack(
              index: _selectedIndex,
              children: [
                LiistView(controller: _scrollControllers[0]),
                LiistView(controller: _scrollControllers[1]),
                LiistView(controller: _scrollControllers[2]),
                LiistView(controller: _scrollControllers[3]),
              ],
            ),
          ),
          // 带动画的AppBar
          AnimatedContainer(
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeInOut,
            height: _isVisible ? appBarHeight : 0,
            child: AppBar(
              title: const Text("AppBar"),
              centerTitle: true,
              elevation: _isVisible ? 4 : 0,
            ),
          ),
          // 带动画的底部导航栏
          AnimatedPositioned(
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeInOut,
            bottom: _isVisible ? 0 : -bottomNavHeight,
            left: 0,
            right: 0,
            child: SizedBox(
              height: bottomNavHeight,
              child: BottomNavigationBar(
                items: const <BottomNavigationBarItem>[
                  BottomNavigationBarItem(
                    icon: Icon(IconlyLight.image),
                    label: '',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(IconlyLight.video),
                    label: '',
                  ),
                  BottomNavigationBarItem(
                    icon: Icons.library_music_outlined,
                    label: '',
                  ),
                  BottomNavigationBarItem(
                    icon: Icon(IconlyLight.profile),
                    label: '',
                  ),
                ],
                currentIndex: _selectedIndex,
                showSelectedLabels: true,
                showUnselectedLabels: false,
                selectedFontSize: 0.0,
                type: BottomNavigationBarType.fixed,
                onTap: _onItemTapped,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

ListView页面(修改后)

class LiistView extends StatelessWidget {
  final ScrollController controller;
  const LiistView({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: controller,
      itemCount: 80,
      itemBuilder: (context, index){
        return const ListTile(
          title: Text("Abcd"),
        );
      },
    );
  }
}

关键实现要点

  • 移除嵌套Scaffold:ListView页面作为导航栏页面body的一部分,无需嵌套Scaffold,避免布局层级混乱
  • 统一管理滚动控制器:为每个页面创建独立的ScrollController,切换页面时监听当前活跃页面的滚动事件
  • 平滑动画过渡:使用AnimatedContainer和AnimatedPositioned实现导航栏的显示/隐藏动画,300ms过渡时长保证流畅性
  • 内容适配:通过Padding动态调整内容区域的上下间距,确保导航栏隐藏时内容全屏显示,显示时不被遮挡

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:30:17