Flutter如何实现 登录页 和 注册页 不显示底部菜单,其他页面显示底部菜单

要实现登录 / 注册页隐藏底部菜单,其他页面显示 ,核心是将底部菜单封装在 "主容器页面" 中,登录 / 注册页作为独立页面通过路由跳转(不嵌套在主容器中)。以下是更通用、易扩展的实现方案,适配多页面场景(首页、产品页、用户信息页等)。

方案整体思路

  1. 分层结构

    • 「基础层」:MaterialApp 管理全局路由。
    • 「主容器层」:MainScaffold 封装底部菜单,仅对非登录 / 注册页显示。
    • 「业务页层」:登录 / 注册页为独立页面(无底部菜单),首页 / 产品页 / 用户信息页嵌套在主容器中(显示底部菜单)。
  2. 路由控制:通过路由名称判断是否显示底部菜单,无需为每个页面单独配置。

完整实现代码

1. 路由常量(统一管理)

dart 复制代码
// lib/routes/app_routes.dart
class AppRoutes {
  // 无需底部菜单的页面
  static const String login = '/login';
  static const String register = '/register';
  
  // 需要底部菜单的页面
  static const String home = '/'; // 首页(默认页)
  static const String product = '/product'; // 产品页
  static const String userInfo = '/userInfo'; // 用户信息页
}

2. 封装带底部菜单的主容器(核心)

dart 复制代码
// lib/widgets/main_scaffold.dart
import 'package:flutter/material.dart';
import '../routes/app_routes.dart';

class MainScaffold extends StatelessWidget {
  final Widget body; // 页面主体内容
  final int currentTabIndex; // 底部菜单选中项
  final Function(int) onTabChanged; // 菜单切换回调

  const MainScaffold({
    super.key,
    required this.body,
    required this.currentTabIndex,
    required this.onTabChanged,
  });

  // 判断当前页面是否需要显示底部菜单
  bool _shouldShowBottomNav(BuildContext context) {
    final routeName = ModalRoute.of(context)?.settings.name ?? '';
    // 排除登录/注册页,其余页面均显示底部菜单
    return ![AppRoutes.login, AppRoutes.register].contains(routeName);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: body,
      // 仅需显示底部菜单时,渲染BottomNavigationBar
      bottomNavigationBar: _shouldShowBottomNav(context)
          ? BottomNavigationBar(
              currentIndex: currentTabIndex,
              onTap: onTabChanged,
              type: BottomNavigationBarType.fixed, // 适配多选项
              items: const [
                BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
                BottomNavigationBarItem(icon: Icon(Icons.shop), label: '产品'),
                BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
              ],
            )
          : null, // 隐藏时设为null(关键:避免留白)
    );
  }
}

3. 登录 / 注册页(独立页面,无底部菜单)

登录页

dart 复制代码
// lib/pages/login_page.dart
import 'package:flutter/material.dart';
import '../routes/app_routes.dart';

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(decoration: const InputDecoration(hintText: '手机号/账号')),
            const SizedBox(height: 16),
            TextField(
              obscureText: true,
              decoration: const InputDecoration(hintText: '密码'),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {
                  // 登录成功:跳转到首页(替换路由栈,避免返回登录页)
                  Navigator.pushReplacementNamed(context, AppRoutes.home);
                },
                child: const Text('登录'),
              ),
            ),
            TextButton(
              onPressed: () {
                // 跳转到注册页
                Navigator.pushNamed(context, AppRoutes.register);
              },
              child: const Text('没有账号?去注册'),
            ),
          ],
        ),
      ),
    );
  }
}

注册页

less 复制代码
// lib/pages/register_page.dart
import 'package:flutter/material.dart';

class RegisterPage extends StatelessWidget {
  const RegisterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('注册')),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(decoration: const InputDecoration(hintText: '手机号')),
            const SizedBox(height: 16),
            TextField(decoration: const InputDecoration(hintText: '验证码')),
            const SizedBox(height: 16),
            TextField(
              obscureText: true,
              decoration: const InputDecoration(hintText: '设置密码'),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {
                  // 注册成功:返回登录页
                  Navigator.pop(context);
                },
                child: const Text('完成注册'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

4. 带底部菜单的业务页面(首页 / 产品页 / 用户信息页)

首页

dart 复制代码
// lib/pages/home_page.dart(底部菜单容器)
import 'package:flutter/material.dart';
import '../widgets/main_scaffold.dart';
import '../routes/app_routes.dart';
import 'product_page.dart';
import 'user_info_page.dart';

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _currentTabIndex = 0; // 当前选中的底部菜单索引

  // 底部菜单对应的页面(可直接跳转其他业务页)
  final List<Widget> _tabPages = const [
    HomeContent(), // 首页内容
    ProductPage(), // 产品页(独立页面,但嵌套在主容器中)
    UserInfoPage(), // 用户信息页
  ];

  // 切换底部菜单
  void _onTabChanged(int index) {
    setState(() {
      _currentTabIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MainScaffold(
      body: _tabPages[_currentTabIndex],
      currentTabIndex: _currentTabIndex,
      onTabChanged: _onTabChanged,
    );
  }
}

// 首页内容
class HomeContent extends StatelessWidget {
  const HomeContent({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('首页(显示底部菜单)'),
          ElevatedButton(
            onPressed: () {
              // 跳转到产品页(仍显示底部菜单)
              Navigator.pushNamed(context, AppRoutes.product);
            },
            child: const Text('去产品页'),
          ),
        ],
      ),
    );
  }
}

产品页

dart 复制代码
// lib/pages/product_page.dart(产品页)
import 'package:flutter/material.dart';

class ProductPage extends StatelessWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('产品页')),
      body: const Center(child: Text('产品页(显示底部菜单)')),
    );
  }
}

用户信息页

dart 复制代码
// lib/pages/user_info_page.dart(用户信息页)
import 'package:flutter/material.dart';
import '../routes/app_routes.dart';

class UserInfoPage extends StatelessWidget {
  const UserInfoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('用户信息页(显示底部菜单)'),
          ElevatedButton(
            onPressed: () {
              // 跳转到登录页(隐藏底部菜单)
              Navigator.pushNamed(context, AppRoutes.login);
            },
            child: const Text('退出登录/去登录'),
          ),
        ],
      ),
    );
  }
}

5. 全局路由配置(入口文件)

dart 复制代码
// lib/main.dart
import 'package:flutter/material.dart';
import 'routes/app_routes.dart';
import 'pages/login_page.dart';
import 'pages/register_page.dart';
import 'pages/home_page.dart';
import 'pages/product_page.dart';
import 'pages/user_info_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '底部菜单控制示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      // 全局路由表
      routes: {
        AppRoutes.home: (context) => const HomePage(),
        AppRoutes.login: (context) => const LoginPage(),
        AppRoutes.register: (context) => const RegisterPage(),
        AppRoutes.product: (context) => const ProductPage(),
        AppRoutes.userInfo: (context) => const UserInfoPage(),
      },
      initialRoute: AppRoutes.home, // 初始页面为首页
      // 可选:路由跳转动画(保持统一体验)
      pageTransitionsTheme: const PageTransitionsTheme(
        builders: {
          TargetPlatform.android: CupertinoPageTransitionsBuilder(),
          TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
        },
      ),
    );
  }
}

关键细节说明

  1. 底部菜单显示逻辑

    • MainScaffold 中通过 _shouldShowBottomNav 方法,根据当前路由名称判断是否显示底部菜单(仅排除登录 / 注册页)。
    • 底部菜单设为 null 而非 SizedBox.shrink(),避免底部留白。
  2. 页面跳转规则

    • 登录 / 注册页通过 Navigator.pushNamed 跳转,属于独立页面(无主容器包裹,因此无底部菜单)。
    • 首页 / 产品页 / 用户信息页嵌套在 MainScaffold 中,无论是否通过路由跳转(如首页跳产品页),都会显示底部菜单。
    • 登录成功后用 pushReplacementNamed 替换路由栈,避免返回登录页。
  3. 扩展性

    • 新增需要显示底部菜单的页面:只需在路由表中注册,无需修改其他代码(自动继承主容器的底部菜单)。
    • 新增需要隐藏底部菜单的页面:只需在 _shouldShowBottomNav 方法中添加对应的路由名称(如 AppRoutes.forgetPwd)。

优化建议

  1. 登录状态拦截 :结合 onGenerateRoute 实现路由守卫,未登录时访问用户信息页自动跳转到登录页:

    dart

    csharp 复制代码
    // 在MyApp的MaterialApp中添加
    onGenerateRoute: (settings) {
      // 模拟登录状态(实际可通过Provider/Bloc管理)
      bool isLogin = false; 
      if (settings.name == AppRoutes.userInfo && !isLogin) {
        return MaterialPageRoute(builder: (_) => const LoginPage());
      }
      return MaterialPageRoute(builder: (_) => const HomePage());
    },
  2. 底部菜单状态保持 :若需要切换底部菜单时保留页面状态(如产品页滚动位置),可使用 PageView + AutomaticKeepAliveClientMixin 替代 List<Widget>

    dart 复制代码
    // 替换HomePage中的_tabPages为PageView
    PageView(
      controller: PageController(initialPage: _currentTabIndex),
      onPageChanged: _onTabChanged,
      physics: const NeverScrollableScrollPhysics(), // 禁止左右滑动
      children: _tabPages,
    );
  3. 适配不同屏幕 :底部菜单可添加 backgroundColorselectedItemColor 等样式,适配深色模式:

    dart 复制代码
    BottomNavigationBar(
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      selectedItemColor: Theme.of(context).primaryColor,
      unselectedItemColor: Colors.grey,
      // 其他配置...
    );

此方案兼顾了简洁性扩展性,适配多页面场景,且逻辑清晰,易于维护。

相关推荐
小二·10 分钟前
Python Web 开发进阶实战:神经符号系统 —— 在 Flask + Vue 中融合深度学习与知识图谱
前端·python·flask
Yan.love12 分钟前
【CSS-动画与过渡】丝滑的艺术,从简单位移到贝塞尔曲线
前端·css
黎轩栀海13 分钟前
Element-UI 用命令行主题工具修改主题色
前端
梦65025 分钟前
Vue 中 v-for 与 v-if 优先级
前端·javascript·vue.js
一只小bit31 分钟前
Qt 多媒体:快速解决音视频播放问题
前端·c++·qt·音视频·cpp·页面
梦65031 分钟前
React 高阶组件
前端·react.js·前端框架
CHU72903531 分钟前
智慧回收新体验:同城废品回收小程序的便捷功能探索
java·前端·人工智能·小程序·php
Marshmallowc31 分钟前
从URL变化到组件重绘:React Router 状态分发机制与组件挂载逻辑深度全解
前端·react.js·前端框架·react router·组件生命周期
虹少侠1 小时前
基于 WebKit 构建 macOS 多浮窗视频播放的技术实践(含完整产品落地)
前端·macos·swift·webkit
木易 士心1 小时前
Vue 响应式数据失效全解析:从原理机制到工程实践
前端·javascript·vue.js