简单页面跳转
Flutter中跳转页面很简单, 在Flutter中screens和pages都被称为routes,主要用到Navigator, 它维护了一个路由栈, push()的时候就是入栈一个Route对象, 其中Route对象主要是由MaterialPageRoute封装而成的, 定义了要怎么去跳转, 完成跳转返回主要是使用了其中两个方法Navigator.push()和Navigator.pop()
// 第一个页面
class FirstRoute extends StatelessWidget {
const FirstRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("first route"),
),
body: Center(
child: ElevatedButton(
child: const Text("open route"),
onPressed: () {
// 跳转
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SecondRoute()));
},
)),
);
}
}
// 第二个页面
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// appbar 默认会添加一个返回按钮
appBar: AppBar(title: const Text("second page")),
body: Center(
child: ElevatedButton(
child: const Text("back to first"),
onPressed: () {
// 返回到上一个页面
Navigator.pop(context);
},
)),
);
}
}
使用命名(静态)路由进行跳转
上面的例子进行跳转的时候, 都写死了.. 只能跳转到某个页面MaterialPageRoute(builder: (context) => const SecondRoute()), 如果跳转的是同一个页面就会出现很多重复的代码, 要解决这个问题, 就需要使用命名路由Navigator.pushNamed(), 跟上面例子的区别就是, 在MaterialApp的构造器里有一个初始化initialRoute(从哪里开始)和routes(有哪些地址).
tips: 主要差距是在`main()`函数这里
void main() {
runApp(MaterialApp(
// home: FirstRoute(), // 如果定义了routes: 这里就不能再有home, 否则会报错
initialRoute: '/',
routes: {
'/': (context) => const FirstRoute(),
'/second': (context) => const SecondRoute(),
},
));
}
定义好路由之后, 跳转到SecondRoute就需要用另一个方法
Navigator.pushNamed(context, "/second");
通过命名路由在页面之间传递参数
命名路由貌似官方不推荐? 最大的区别是页面要自己持有routeName
先定义一个传递参数的对象
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
第二个页面需要自己持有routeName
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
// 定义route name
static const routeName = '/second';
@override
Widget build(BuildContext context) {
// 获取传递过来的参数
final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
return Scaffold(
appBar: AppBar(title: Text(args.title)),
body: Center(
child: ElevatedButton(
child: Text(args.message),
onPressed: () {
Navigator.pop(context);
},
)),
);
}
}
在MaterialApp中的定义也需要进行改变
void main() {
runApp(MaterialApp(
// home: FirstRoute(), // 如果定义了routes: 这里就不能再有home
initialRoute: '/',
routes: {
'/': (context) => const FirstRoute(),
// '/second': (context) => const SecondRoute(),
SecondRoute.routeName: (context) => const SecondRoute()
},
));
}
跳转的时候就可以传递参数
class FirstRoute extends StatelessWidget {
const FirstRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("first route"),
),
body: Center(
child: ElevatedButton(
child: const Text("open route"),
onPressed: () {
// Navigator.pushNamed(context, "/second");
Navigator.pushNamed(
context,
SecondRoute.routeName,
arguments: ScreenArguments("param title", "param message"));
},
)),
);
}
}
也可以使用onGenerateRoute来传递参数, 这个要依赖RouteSettings
import 'package:flutter/material.dart';
class ScreenArguments {
final String title;
final String message;
ScreenArguments(this.title, this.message);
}
class FirstRoute extends StatelessWidget {
static const routeName = "/first";
const FirstRoute({super.key});
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;
return Scaffold(
appBar: AppBar(title: Text(args.title)),
body: Center(child: Text(args.message)),
);
}
}
class SecondRoute extends StatelessWidget {
static const routeName = "/second";
final String title;
final String message;
const SecondRoute({super.key, required this.title, required this.message});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(child: Text(message)),
);
}
}
class HomeRoute extends StatelessWidget {
const HomeRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Home route")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, FirstRoute.routeName,
arguments: ScreenArguments("title", "message"));
},
child: Text('navigate to first')),
SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, SecondRoute.routeName,
arguments: ScreenArguments(
"this navi title", "this navi message"));
},
child: Text('navigate to second')),
],
),
),
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
FirstRoute.routeName: (context) => const FirstRoute(),
},
onGenerateRoute: ((settings) {
// 判断是哪个路由, 然后进行赋值
if (settings.name == SecondRoute.routeName) {
final args = settings.arguments as ScreenArguments;
return MaterialPageRoute(builder: ((context) {
return SecondRoute(title: args.title, message: args.message);
}));
}
}),
title: "home route",
home: const HomeRoute(),
);
}
}
从返回的屏幕获取数据
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
),
);
}
// Home screen
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Returning Data Demo'),
),
body: const Center(
child: SelectionButton(),
),
);
}
}
// tips: 这里继承的是 StatefulWidget
class SelectionButton extends StatefulWidget {
const SelectionButton({super.key});
@override
State<SelectionButton> createState() => _SelectionButtonState();
}
class _SelectionButtonState extends State<SelectionButton> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: const Text('Pick an option, any option!'),
);
}
// 异步等待
Future<void> _navigateAndDisplaySelection(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
// 这个应该是判断当前页面有没有被渲染(显示)?
if (!mounted) return;
// 显示snackBar的时候, 先把之前的都给隐藏掉
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('$result')));
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Yep!');
},
child: const Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Nope.');
},
child: const Text('Nope.'),
),
)
],
),
),
);
}
}
传递数据给新的页面
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
const Todo(this.title, this.description);
}
void main() {
runApp(
MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
),
);
}
class TodosScreen extends StatelessWidget {
const TodosScreen({super.key, required this.todos});
final List<Todo> todos;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailScreen(),
// 传递数据的时候还是需要 MaterialPageRoute的包装
// 只不过数据包装是要通过 RouteSettings 里面的 arguments来进行的
settings: RouteSettings(
arguments: todos[index],
),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key});
@override
Widget build(BuildContext context) {
// 这里接收数据 就是通过 settings.arguments 来做
final todo = ModalRoute.of(context)!.settings.arguments as Todo;
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}