Flutter 路由导航完全指南
·
Flutter 路由导航完全指南
引言
路由导航是移动应用的核心功能之一。Flutter 提供了强大且灵活的路由导航系统。本文将深入探讨 Flutter 路由导航的各种用法和高级技巧。
基础概念回顾
路由类型
- MaterialPageRoute: Material风格路由
- CupertinoPageRoute: iOS风格路由
- PageRouteBuilder: 自定义路由
基本导航
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
Navigator.pop(context);
高级技巧一:命名路由
注册路由
void main() {
runApp(MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
},
));
}
使用命名路由
Navigator.pushNamed(context, '/second');
Navigator.pop(context);
高级技巧二:传递参数
方式一:构造函数传参
class SecondScreen extends StatelessWidget {
final String message;
const SecondScreen({super.key, required this.message});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Second Screen')),
body: Center(child: Text(message)),
);
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SecondScreen(message: 'Hello from Home'),
),
);
方式二:通过 settings 传参
Navigator.pushNamed(
context,
'/second',
arguments: 'Hello from Home',
);
// 在接收页面
final message = ModalRoute.of(context)?.settings.arguments as String;
方式三:传递复杂对象
class User {
final String name;
final int age;
const User({required this.name, required this.age});
}
Navigator.pushNamed(
context,
'/profile',
arguments: const User(name: 'John', age: 30),
);
// 在接收页面
final user = ModalRoute.of(context)?.settings.arguments as User;
高级技巧三:返回数据
返回简单数据
// 在 SecondScreen 中
ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Data from Second');
},
child: const Text('Return'),
);
// 在 HomeScreen 中
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondScreen()),
);
print(result); // 'Data from Second'
返回复杂数据
// 返回 Map
Navigator.pop(context, {'status': 'success', 'data': 'value'});
// 返回自定义对象
Navigator.pop(context, const User(name: 'John', age: 30));
高级技巧四:自定义路由动画
淡入淡出动画
Route fadeRoute(Widget page) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = 0.0;
const end = 1.0;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return FadeTransition(
opacity: animation.drive(tween),
child: child,
);
},
);
}
Navigator.push(context, fadeRoute(const SecondScreen()));
滑动动画
Route slideRoute(Widget page) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}
缩放动画
Route scaleRoute(Widget page) {
return PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return ScaleTransition(
scale: animation,
child: child,
);
},
);
}
高级技巧五:嵌套导航
Tab 导航
class TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final Widget child;
const TabNavigator({
super.key,
required this.navigatorKey,
required this.child,
});
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(builder: (context) => child);
},
);
}
}
主页面
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _tab1NavigatorKey = GlobalKey<NavigatorState>();
final _tab2NavigatorKey = GlobalKey<NavigatorState>();
int _currentIndex = 0;
void _onTabTapped(int index) {
if (index == _currentIndex) {
// 双击回到根页面
if (index == 0) {
_tab1NavigatorKey.currentState?.popUntil((route) => route.isFirst);
} else {
_tab2NavigatorKey.currentState?.popUntil((route) => route.isFirst);
}
} else {
setState(() => _currentIndex = index);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: [
TabNavigator(
navigatorKey: _tab1NavigatorKey,
child: const FirstTab(),
),
TabNavigator(
navigatorKey: _tab2NavigatorKey,
child: const SecondTab(),
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onTabTapped,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
],
),
);
}
}
实战案例:登录流程
class AuthService {
static Future<bool> login(String email, String password) async {
await Future.delayed(const Duration(seconds: 2));
return email.isNotEmpty && password.isNotEmpty;
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
Future<void> _login() async {
setState(() => _isLoading = true);
try {
final success = await AuthService.login(
_emailController.text,
_passwordController.text,
);
if (success) {
if (mounted) {
Navigator.pushReplacementNamed(context, '/home');
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login failed')),
);
}
} finally {
setState(() => _isLoading = false);
}
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _login,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
],
),
),
);
}
}
实战案例:路由守卫
class AuthGuard extends StatelessWidget {
final Widget child;
const AuthGuard({super.key, required this.child});
@override
Widget build(BuildContext context) {
final isLoggedIn = AuthService.isLoggedIn;
if (!isLoggedIn) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacementNamed(context, '/login');
});
return const SizedBox();
}
return child;
}
}
// 使用
MaterialApp(
routes: {
'/home': (context) => const AuthGuard(child: HomeScreen()),
'/profile': (context) => const AuthGuard(child: ProfileScreen()),
},
);
实战案例:路由管理类
class AppRouter {
static Future push(BuildContext context, Widget page) {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
);
}
static Future pushNamed(BuildContext context, String routeName, {Object? arguments}) {
return Navigator.pushNamed(context, routeName, arguments: arguments);
}
static void pop(BuildContext context, {Object? result}) {
Navigator.pop(context, result);
}
static void popUntilFirst(BuildContext context) {
Navigator.popUntil(context, (route) => route.isFirst);
}
static void replace(BuildContext context, Widget page) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => page),
);
}
static void replaceNamed(BuildContext context, String routeName, {Object? arguments}) {
Navigator.pushReplacementNamed(context, routeName, arguments: arguments);
}
}
常见问题与解决方案
Q1:如何返回数据?
A:使用 Navigator.pop(context, data):
Navigator.pop(context, 'result');
final result = await Navigator.push(...);
Q2:如何替换路由?
A:使用 pushReplacement:
Navigator.pushReplacement(context, MaterialPageRoute(...));
Q3:如何返回根页面?
A:使用 popUntil:
Navigator.popUntil(context, (route) => route.isFirst);
最佳实践
1. 使用命名路由
MaterialApp(
routes: {
'/': (context) => const HomeScreen(),
'/second': (context) => const SecondScreen(),
},
);
2. 统一路由管理
class Routes {
static const String home = '/';
static const String second = '/second';
static const String profile = '/profile';
}
3. 使用类型安全的参数传递
class SecondScreenArguments {
final String message;
final int count;
const SecondScreenArguments({required this.message, required this.count});
}
Navigator.pushNamed(
context,
Routes.second,
arguments: const SecondScreenArguments(message: 'Hello', count: 5),
);
总结
Flutter 的路由导航系统非常强大。通过本文的学习,你应该能够:
- 使用基本的导航方法
- 使用命名路由
- 传递和返回数据
- 创建自定义路由动画
- 实现嵌套导航
- 创建路由守卫
掌握这些技巧,能够帮助你构建更加复杂和用户友好的应用。
更多推荐
所有评论(0)