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

Flutter:使用OpenContainer动画时隐藏persistent_bottom_nav_bar的问题

问题:PersistentBottomNavBar 结合 OpenContainer 导航时 AppBar 和底部栏未隐藏

使用 persistent_bottom_nav_bar: 6.2.1 实现底部导航,同时尝试通过 OpenContainer 实现带动画的页面跳转,但跳转后原页面的 AppBar 和 BottomNavigationBar 仍然可见。曾尝试组合 openContainer() 和 PersistentNavBarNavigator.pushNewScreen(),但导致页面被推送两次,不符合需求。


解决方案

问题根源是外层 Scaffold 包含了全局 AppBar 和 PersistentTabView,使用 OpenContainer 默认导航时,会在该 Scaffold 的内部 Navigator 栈中跳转,导致原导航栏组件仍处于上层上下文。以下两种方法可解决:

方法一:将 AppBar 移至 Tab 页面内部

这是最简洁的方案,符合 PersistentBottomNavBar 的设计规范:

  1. 移除外层 Scaffold 的 AppBar
    修改 _DashboardView 的 build 方法:
@override
Widget build(BuildContext context) {
  return Scaffold(
    // 移除全局 AppBar
    body: PersistentTabView(
      context,
      controller: _controller,
      screens: _buildScreens(),
      items: _navBarsItems(),
      // 保留原有的其他配置
      handleAndroidBackButtonPress: true,
      resizeToAvoidBottomInset: true,
      stateManagement: true,
      hideNavigationBarWhenKeyboardAppears: true,
      padding: const EdgeInsets.only(top: 8),
      backgroundColor: ColorConstant.secondary,
      isVisible: true,
      animationSettings: const NavBarAnimationSettings(
        navBarItemAnimation: ItemAnimationSettings(
          duration: Duration(milliseconds: 400),
          curve: Curves.ease,
        ),
        screenTransitionAnimation: ScreenTransitionAnimationSettings(
          animateTabTransition: true,
          duration: Duration(milliseconds: 200),
          screenTransitionAnimationType: ScreenTransitionAnimationType.fadeIn,
        ),
      ),
      confineToSafeArea: true,
      navBarHeight: kBottomNavigationBarHeight,
      navBarStyle: NavBarStyle.style3,
    ),
  );
}
  1. 在每个 Tab 页面中添加 AppBar
    以 HomeScreen 为例,将原全局的 DynamicAppBar 移至页面内部:
class HomeScreen extends StatelessWidget {
  final ScrollController scrollController;
  const HomeScreen({super.key, required this.scrollController});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: DynamicAppBar(
        backgroundColor: ColorConstant.secondary,
        title: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Image.asset(AssetConstant.appLogo, width: 30),
            const SizedBox(width: 8),
            Text("GEBOK", style: TextStyle(color: ColorConstant.primary, fontWeight: FontWeight.bold, fontSize: 18)),
          ],
        ),
        trailingWidgets: [
          // 修正搜索按钮的 OpenContainer 导航
          OpenContainer(
            transitionType: ContainerTransitionType.fadeThrough,
            transitionDuration: Duration(milliseconds: 600),
            closedElevation: 0,
            closedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
            openBuilder: (context, _) => SearchScreen(),
            closedBuilder: (context, openContainer) {
              return IconButton(
                icon: const Icon(Icons.search, color: ColorConstant.gray),
                onPressed: openContainer,
              );
            },
          ),
          // 保留原 PopupMenuButton
          PopupMenuButton<String>(
            offset: const Offset(0, 60),
            color: ColorConstant.secondary,
            tooltip: "Menu",
            onSelected: (val) async {
              if (val == "logout") {
                await AuthLocal().clearAuthData();
                Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => LoginScreen()), (route) => false);
              } else if (val == "info") {
                Navigator.push(
                  context,
                  PageTransition(
                    type: PageTransitionType.rightToLeft,
                    duration: const Duration(milliseconds: 500),
                    child: const InfoAppScreen(),
                  ),
                );
              }
            },
            itemBuilder: (BuildContext context) => const <PopupMenuEntry<String>>[
              PopupMenuItem<String>(
                value: 'logout',
                child: Row(children: [Icon(Icons.exit_to_app_outlined), SizedBox(width: 4), Text('Logout')]),
              ),
              PopupMenuItem<String>(
                value: 'info',
                child: Row(children: [Icon(Icons.info_outline), SizedBox(width: 4), Text('Info App')]),
              ),
            ],
          ),
        ],
      ),
      body: // 原 HomeScreen 的页面内容
    );
  }
}
  1. 修正书籍卡片的 OpenContainer
    此时直接使用 OpenContainer 的默认导航即可,跳转后会自动脱离原 Tab 上下文,隐藏底部导航栏和当前页面的 AppBar:
OpenContainer(
  transitionType: ContainerTransitionType.fadeThrough,
  transitionDuration: const Duration(milliseconds: 500),
  openBuilder: (context, _) => BookDetailScreen(bookId: item.id),
  closedElevation: 0,
  closedColor: Colors.transparent,
  closedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
  closedBuilder: (context, openContainer) {
    return Material(
      color: Colors.transparent,
      child: InkWell(
        onTap: openContainer,
        borderRadius: BorderRadius.circular(8),
        child: <my content>,
      ),
    );
  },
),

方法二:结合 PersistentNavBarNavigator 与 OpenContainer 动画

若不想调整页面结构,可通过 PersistentNavBarNavigator 处理导航,同时保留 OpenContainer 的转场动画:

修改书籍卡片的 OpenContainer 代码:

OpenContainer(
  transitionType: ContainerTransitionType.fadeThrough,
  transitionDuration: const Duration(milliseconds: 500),
  closedElevation: 0,
  closedColor: Colors.transparent,
  closedShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
  closedBuilder: (context, openContainer) {
    return Material(
      color: Colors.transparent,
      child: InkWell(
        onTap: () {
          // 使用 PersistentBottomNavBar 的导航方法,禁用默认页面动画
          PersistentNavBarNavigator.pushNewScreen(
            context,
            screen: OpenContainer(
              openBuilder: (_, __) => BookDetailScreen(bookId: item.id),
              closedBuilder: (_, ___) => Container(),
              transitionType: ContainerTransitionType.fadeThrough,
              transitionDuration: const Duration(milliseconds: 500),
              openElevation: 0,
            ),
            withNavBar: false,
            pageTransitionAnimation: PageTransitionAnimation.none,
          );
        },
        borderRadius: BorderRadius.circular(8),
        child: <my content>,
      ),
    );
  },
  openBuilder: (context, _) => Container(),
),

说明

  • 方法一逻辑清晰,符合组件分层设计,是推荐方案
  • 方法二无需改动现有页面布局,但代码复杂度较高,适合临时场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:34:55