要实现登录 / 注册页隐藏底部菜单,其他页面显示 ,核心是将底部菜单封装在 "主容器页面" 中,登录 / 注册页作为独立页面通过路由跳转(不嵌套在主容器中)。以下是更通用、易扩展的实现方案,适配多页面场景(首页、产品页、用户信息页等)。
方案整体思路
-
分层结构:
- 「基础层」:
MaterialApp管理全局路由。 - 「主容器层」:
MainScaffold封装底部菜单,仅对非登录 / 注册页显示。 - 「业务页层」:登录 / 注册页为独立页面(无底部菜单),首页 / 产品页 / 用户信息页嵌套在主容器中(显示底部菜单)。
- 「基础层」:
-
路由控制:通过路由名称判断是否显示底部菜单,无需为每个页面单独配置。
完整实现代码
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(),
},
),
);
}
}
关键细节说明
-
底部菜单显示逻辑:
MainScaffold中通过_shouldShowBottomNav方法,根据当前路由名称判断是否显示底部菜单(仅排除登录 / 注册页)。- 底部菜单设为
null而非SizedBox.shrink(),避免底部留白。
-
页面跳转规则:
- 登录 / 注册页通过
Navigator.pushNamed跳转,属于独立页面(无主容器包裹,因此无底部菜单)。 - 首页 / 产品页 / 用户信息页嵌套在
MainScaffold中,无论是否通过路由跳转(如首页跳产品页),都会显示底部菜单。 - 登录成功后用
pushReplacementNamed替换路由栈,避免返回登录页。
- 登录 / 注册页通过
-
扩展性:
- 新增需要显示底部菜单的页面:只需在路由表中注册,无需修改其他代码(自动继承主容器的底部菜单)。
- 新增需要隐藏底部菜单的页面:只需在
_shouldShowBottomNav方法中添加对应的路由名称(如AppRoutes.forgetPwd)。
优化建议
-
登录状态拦截 :结合
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()); }, -
底部菜单状态保持 :若需要切换底部菜单时保留页面状态(如产品页滚动位置),可使用
PageView + AutomaticKeepAliveClientMixin替代List<Widget>:dart// 替换HomePage中的_tabPages为PageView PageView( controller: PageController(initialPage: _currentTabIndex), onPageChanged: _onTabChanged, physics: const NeverScrollableScrollPhysics(), // 禁止左右滑动 children: _tabPages, ); -
适配不同屏幕 :底部菜单可添加
backgroundColor、selectedItemColor等样式,适配深色模式:dartBottomNavigationBar( backgroundColor: Theme.of(context).scaffoldBackgroundColor, selectedItemColor: Theme.of(context).primaryColor, unselectedItemColor: Colors.grey, // 其他配置... );
此方案兼顾了简洁性 和扩展性,适配多页面场景,且逻辑清晰,易于维护。