Flutter的导航基于栈模型:push压入新页面,pop弹出当前页面。
// ✅ 验证通过:Flutter 3.22
// 文件:lib/navigation_basics.dart
import 'package:flutter/material.dart';
void main() => runApp(const NavigationApp());
class NavigationApp extends StatelessWidget {
const NavigationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '路由导航示例',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
/// 首页
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 1. 基本push — 直接创建目标页面
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const DetailPage(title: '直接跳转'),
),
);
},
child: const Text('Push到详情页'),
),
const SizedBox(height: 12),
// 2. push带参数
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DetailPage(
title: '带参数跳转',
data: {'userId': 42, 'name': 'Alice'},
),
),
);
},
child: const Text('带参数跳转'),
),
const SizedBox(height: 12),
// 3. push接收返回值
ElevatedButton(
onPressed: () async {
var result = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const PickColorPage(),
),
);
if (result != null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('你选择了: $result')),
);
}
},
child: const Text('选择颜色(接收返回值)'),
),
const SizedBox(height: 12),
// 4. pushReplacement — 替换当前页面
ElevatedButton(
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const DetailPage(title: '替换页面'),
),
);
},
child: const Text('PushReplacement'),
),
const SizedBox(height: 24),
const Text('其他导航方式:'),
const SizedBox(height: 12),
// 5. pushAndRemoveUntil — 清除栈
ElevatedButton(
onPressed: () {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const HomePage()),
(route) => false, // 清除所有之前的路由
);
},
child: const Text('清空栈跳转'),
),
// 6. pop — 返回上一页
// Navigator.of(context).pop();
// Navigator.of(context).pop(resultValue);
// 7. maybePop — 安全返回(可能不执行)
// Navigator.of(context).maybePop();
// 8. canPop — 是否可以返回
// var canGoBack = Navigator.of(context).canPop();
],
),
),
);
}
}
/// 详情页
class DetailPage extends StatelessWidget {
final String title;
final Map<String, dynamic>? data;
const DetailPage({required this.title, this.data, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('详情页: $title', style: Theme.of(context).textTheme.headlineSmall),
if (data != null) ...[
const SizedBox(height: 16),
Text('参数: $data'),
],
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('返回'),
),
],
),
),
);
}
}
/// 颜色选择页 — 演示返回值
class PickColorPage extends StatelessWidget {
const PickColorPage({super.key});
@override
Widget build(BuildContext context) {
var colors = {
'红色': Colors.red,
'蓝色': Colors.blue,
'绿色': Colors.green,
'紫色': Colors.purple,
'橙色': Colors.orange,
};
return Scaffold(
appBar: AppBar(title: const Text('选择颜色')),
body: ListView(
children: colors.entries.map((entry) => ListTile(
leading: CircleAvatar(backgroundColor: entry.value),
title: Text(entry.key),
trailing: const Icon(Icons.check_circle_outline),
onTap: () => Navigator.of(context).pop(entry.key),
)).toList(),
),
);
}
}
// ✅ 验证通过
/// Dialog — 模态对话框
void dialogDemos(BuildContext context) {
// 1. AlertDialog
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认操作'),
content: const Text('确定要删除这个项目吗?此操作不可撤销。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('删除'),
),
],
),
);
// 2. SimpleDialog — 选项列表
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: const Text('选择选项'),
children: [
SimpleDialogOption(
onPressed: () => Navigator.pop(context, 'option1'),
child: const Text('选项1'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(context, 'option2'),
child: const Text('选项2'),
),
],
),
);
// 3. BottomSheet
showModalBottomSheet(
context: context,
isScrollControlled: true, // 全屏底部表单
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) => Padding(
padding: EdgeInsets.fromLTRB(
16, 16, 16, MediaQuery.of(context).viewInsets.bottom + 16,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40, height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 16),
const TextField(decoration: InputDecoration(labelText: '备注')),
const SizedBox(height: 12),
FilledButton(
onPressed: () => Navigator.pop(context),
child: const Text('提交'),
),
],
),
),
);
}
/// 自定义Dialog路由过渡动画
class SlideDialog extends PopupRoute {
final Widget child;
SlideDialog({required this.child});
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return child;
}
@override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic)),
child: child,
);
}
@override
Color? get barrierColor => Colors.black54;
@override
bool get barrierDismissible => true;
@override
String? get barrierLabel => 'SlideDialog';
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
@override
Duration get reverseTransitionDuration => const Duration(milliseconds: 200);
}
// ✅ 验证通过
/// 自定义页面转场
// 1. PageRouteBuilder
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(title: '自定义转场'),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// 淡入淡出
return FadeTransition(opacity: animation, child: child);
// 缩放
// return ScaleTransition(scale: animation, child: child);
// 旋转+缩放
// return RotationTransition(turns: animation, child: ScaleTransition(scale: animation, child: child));
// 滑动
// var tween = Tween(begin: const Offset(1.0, 0.0), end: Offset.zero)
// .chain(CurveTween(curve: Curves.easeInOutCubic));
// return SlideTransition(position: animation.drive(tween), child: child);
},
transitionDuration: const Duration(milliseconds: 400),
reverseTransitionDuration: const Duration(milliseconds: 300),
),
);
// 2. CupertinoPageRoute — iOS风格转场
// Navigator.of(context).push(
// CupertinoPageRoute(builder: (_) => const NextPage()),
// );
// 3. Hero动画(下一课详细讲解)
// 只需在两个页面中用相同的heroTag标记Widget
// ✅ 验证通过
/// 路由守卫——拦截未登录用户
class AuthGuard {
static bool _isLoggedIn = false;
static Route<dynamic>? onGenerateRoute(RouteSettings settings) {
// 需要认证的页面列表
var protectedRoutes = ['/profile', '/settings', '/orders'];
if (protectedRoutes.contains(settings.name) && !_isLoggedIn) {
// 重定向到登录页
return MaterialPageRoute(
builder: (_) => const LoginPage(),
settings: settings, // 保留原始settings
);
}
// 正常路由
return null; // 让其他路由处理器处理
}
}
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Center(
child: ElevatedButton(
onPressed: () {
AuthGuard._isLoggedIn = true;
Navigator.of(context).pushReplacementNamed('/profile');
},
child: const Text('模拟登录'),
),
),
);
}
}
实现3步注册向导,每步push新页面,最后一步完成后pop回首页。
实现一个从底部滑入的分享面板,包含微信/微博/QQ等分享选项。
使用RouteObserver追踪页面访问,实现页面浏览统计。
下一课:命名路由——集中管理路由表,实现声明式导航。