Flutter 导航路由:构建流畅的应用导航体验
代码如诗,导航如画。让我们用 Flutter 路由的强大能力,构建出既流畅又直观的应用导航系统。
什么是 Flutter 导航?
Flutter 导航是指在不同页面(或称为路由)之间进行切换的机制。良好的导航系统是应用用户体验的重要组成部分,它决定了用户如何在应用中浏览和找到所需内容。
基础导航
1. 使用 Navigator
dart
import 'package:flutter/material.dart';
// 跳转到新页面
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
// 返回上一页
Navigator.pop(context);
// 替换当前页面
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
// 清除所有页面并跳转到新页面
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false,
);
2. 命名路由
dart
// 在 MaterialApp 中定义路由
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/second': (context) => SecondPage(),
'/third': (context) => ThirdPage(),
},
);
}
}
// 使用命名路由导航
Navigator.pushNamed(context, '/second');
// 返回上一页
Navigator.pop(context);
// 替换当前页面
Navigator.pushReplacementNamed(context, '/second');
// 清除所有页面并跳转到新页面
Navigator.pushNamedAndRemoveUntil(context, '/', (route) => false);
3. 传递参数
dart
// 跳转到新页面并传递参数
Navigator.pushNamed(
context,
'/second',
arguments: {'id': 1, 'name': 'Flutter'},
);
// 在新页面接收参数
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Map<String, dynamic> args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final int id = args['id'];
final String name = args['name'];
return Scaffold(
appBar: AppBar(title: Text('Second Page')),
body: Center(
child: Text('ID: $id, Name: $name'),
),
);
}
}
高级路由管理
1. 使用 onGenerateRoute
dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/second':
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (context) => SecondPage(
id: args['id'],
name: args['name'],
),
);
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
},
);
}
}
2. 自定义路由动画
dart
// 自定义页面路由
class CustomPageRoute<T> extends PageRouteBuilder<T> {
final Widget child;
CustomPageRoute({required this.child})
: super(
pageBuilder: (context, animation, secondaryAnimation) => child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var offsetAnimation = animation.drive(tween);
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}
// 使用自定义路由
Navigator.push(
context,
CustomPageRoute(child: SecondPage()),
);
3. 使用 PageRouteBuilder
dart
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 500),
),
);
路由守卫和拦截
1. 登录验证
dart
class AuthGuard {
static bool isLoggedIn = false;
static Route<dynamic>? onGenerateRoute(RouteSettings settings) {
// 需要登录的页面
final authRoutes = ['/profile', '/settings'];
if (authRoutes.contains(settings.name) && !isLoggedIn) {
return MaterialPageRoute(
builder: (context) => LoginPage(),
settings: RouteSettings(name: '/login'),
);
}
// 正常路由
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => HomePage());
case '/login':
return MaterialPageRoute(builder: (context) => LoginPage());
case '/profile':
return MaterialPageRoute(builder: (context) => ProfilePage());
default:
return MaterialPageRoute(builder: (context) => NotFoundPage());
}
}
}
2. 路由拦截器
dart
class RouteInterceptor extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
print('Pushed: ${route.settings.name}');
// 可以在这里进行埋点、权限检查等
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
print('Popped: ${route.settings.name}');
}
}
// 在 MaterialApp 中使用
MaterialApp(
navigatorObservers: [RouteInterceptor()],
// ...
)
底部导航栏
1. 基础底部导航
dart
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
final List<Widget> _pages = [
HomeTab(),
SearchTab(),
ProfileTab(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '搜索',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '我的',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.blue,
onTap: _onItemTapped,
),
);
}
}
2. 使用 IndexedStack 保持页面状态
dart
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
final List<Widget> _pages = [
HomeTab(),
SearchTab(),
ProfileTab(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '搜索',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '我的',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.blue,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
),
);
}
}
抽屉导航
dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'菜单',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
ListTile(
leading: Icon(Icons.home),
title: Text('首页'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/');
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/settings');
},
),
],
),
),
body: Center(child: Text('首页内容')),
);
}
}
最佳实践
- 使用命名路由:便于管理和维护
- 参数类型安全:使用强类型传递参数
- 路由守卫:在关键页面进行权限验证
- 动画一致性:保持应用内导航动画的一致性
- 状态保持:使用 IndexedStack 保持页面状态
- 错误处理:处理路由不存在的情况
实践案例:完整的导航系统
dart
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navigation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/second': (context) => SecondPage(),
'/third': (context) => ThirdPage(),
},
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(),
);
},
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('首页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'首页',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/second',
arguments: {'message': '来自首页的问候'},
);
},
child: Text('跳转到第二页'),
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final message = args['message'];
return Scaffold(
appBar: AppBar(
title: Text('第二页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'第二页',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 10),
Text(
message,
style: TextStyle(fontSize: 16, color: Colors.grey),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/third');
},
child: Text('跳转到第三页'),
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回上一页'),
),
],
),
),
);
}
}
class ThirdPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('第三页'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'第三页',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pushNamedAndRemoveUntil(
context,
'/',
(route) => false,
);
},
child: Text('返回首页'),
),
],
),
),
);
}
}
class NotFoundPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('页面未找到'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
SizedBox(height: 16),
Text(
'404',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
'页面未找到',
style: TextStyle(fontSize: 18),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('返回首页'),
),
],
),
),
);
}
}
总结
Flutter 的导航系统提供了丰富的功能来构建流畅的应用导航体验。通过合理使用命名路由、自定义动画、路由守卫等技术,我们可以创建出既美观又实用的导航系统。
导航不仅仅是页面的切换,更是用户体验的引导。让我们用 Flutter 路由的强大能力,构建出令人惊叹的应用导航系统,展现前端技术的无限可能。