大多数应用程序都具有多个Page或View,并且希望将用户从当前Page平滑过渡到另一个页面。Flutter的路由和导航功能可帮助您管理应用中屏幕之间的命名和过渡。
概述
管理多个页面时有两个核心概念和类:Route和 Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。
-
从First页面导航到second页面并返回FIrst页面:
void main() => runApp(new MaterialApp( title: 'Navigation Basics', home: FirstRoute(), )); class FirstRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('First Route'), ), body: Center( child: RaisedButton( child: Text('Open route'), onPressed: () { // Navigate to second route when tapped. Navigator.of(context).push(new MaterialPageRoute( builder: (BuildContext context) => SecondRoute())); }, ), ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Second Route"), ), body: Center( child: RaisedButton( onPressed: () { // Navigate back to first route when tapped. Navigator.pop(context); }, child: Text('Go back!'), ), ), ); } }
从A页面发送数据去B的页面,实际上是通过构造函数传递数据:
class Todo {
final String title;
final String describe;
Todo(this.title, this.describe);
}
class TodosScreen extends StatelessWidget {
final List<Todo> todos;
const TodosScreen({Key key, this.todos}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Todos'),
),
body: new ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text('todos[index].title'),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => new DetailScreen(
todo: todos[index],
)));
},
);
}),
);
}
}
class DetailScreen extends StatelessWidget {
final Todo todo;
const DetailScreen({Key key, this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('${todo.title}'),
),
body: new Padding(
padding: EdgeInsets.all(16.0),
child: new Text(todo.describe),
),
);
}
}
- 等待其他页面返回数据
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
///运行SelectionScreen页面并且等待SelectionScreen Navigator.pop!返回结果
void _navigateAndDisplaySelection(BuildContext context) async {
///Navigator.push 将在我们调用SelectionScreen页面的Navigator.pop完成后返回一个携带结果数据Future
final result =
await Navigator.push(context, new MaterialPageRoute(builder: (context) {
return SelectionScreen();
}));
// String valResult = await result;
///之后等待SelectionScreen页面返回结果,隐藏任意上一个的SnackBars,并显示并将结果显示在SnackBar
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(new SnackBar(content: new Text(result)));
}
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
- 通过名字路由到其他页面
void main() {
runApp(MaterialApp(
title: 'Named Routes Demo',
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
));
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
// Navigate to the second screen using a named route
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
- 跳转页面添加过度动画
class HeroApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Transition Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: Image.network(
'http://pic37.nipic.com/20140113/8800276_184927469000_2.png',
),
),
///transitionsBuilder
///I/flutter (21575): pageBuilder
///I/flutter (21575): transitionsBuilder
///I/flutter (21575): transitionsBuilder
///I/flutter (21575): transitionsBuilder
onTap: () {
Navigator.push(
context,
new PageRouteBuilder(
transitionDuration: new Duration(seconds: 2),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
print('transitionsBuilder 这里会一直执行到动画结束');
// return SlideTransition(
// position: new Tween<Offset>(
// begin: const Offset(0.0, 1.0),
// end: Offset.zero,
// ).animate(animation),
// child: SlideTransition(
// position: Tween<Offset>(
// begin: Offset.zero,
// end: const Offset(0.0, 1.0),
// ).animate(secondaryAnimation),
// child: child,
// ),
// );
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child, // child is the value returned by pageBuilder
);
},
pageBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation) {
print('pageBuilder');
return new DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network(
'http://pic37.nipic.com/20140113/8800276_184927469000_2.png',
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
-
自定义Router
class CustomRoute extends PageRouteBuilder { final Widget widget; CustomRoute(this.widget) : super( // 设置过度时间 transitionDuration: Duration(seconds: 1), // 构造器 pageBuilder: ( // 上下文和动画 BuildContext context, Animation<double> animaton1, Animation<double> animaton2, ) { return widget; }, transitionsBuilder: ( BuildContext context, Animation<double> animaton1, Animation<double> animaton2, Widget child, ) { // 旋转加缩放动画效果 return RotationTransition( turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animaton1, curve: Curves.fastOutSlowIn, )), child: ScaleTransition( scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animaton1, curve: Curves.fastOutSlowIn)), child: child, ), ); }); }
使用很简单:
final result =
await Navigator.push(context, new CustomRoute(SelectionScreen()));
// String valResult = await result;
///之后等待SelectionScreen页面返回结果,隐藏任意上一个的SnackBars,并显示并将结果显示在SnackBar
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(new SnackBar(content: new Text(result)));
实际项目中使用配置和配置:
void main() {
var widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
widgetsBinding.addObserver(AppObserver());
///在当前 Frame 绘制完成后回调,并且只会回调一次
widgetsBinding.addPostFrameCallback((duration) {
print('onPostFrameCallback>>${duration.toString()}');
});
///会在每次绘制 Frame 结束后回调,可用来监测 FPS
widgetsBinding.addPersistentFrameCallback((timeStamp) {});
runApp(const TripApp());
}
class AppObserver with WidgetsBindingObserver {
// 页面 pop
@override
Future<bool> didPopRoute() => Future<bool>.value(false);
// 页面 push
@override
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系统窗口相关改变回调,如旋转
@override
void didChangeMetrics() {
print('didChangeMetrics');
}
// 文本缩放系数变化
@override
void didChangeTextScaleFactor() {
print('didChangeTextScaleFactor');
}
// 系统亮度变化
@override
void didChangePlatformBrightness() {
print('didChangePlatformBrightness');
}
// 本地化语言变化
@override
void didChangeLocales(List<Locale>? locale) {
print('didChangeLocales');
}
// App 生命周期变化
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('didChangeAppLifecycleState>>${state.name}');
}
// 内存警告回调
@override
void didHaveMemoryPressure() {
print('didHaveMemoryPressure');
}
// Accessibility 相关特性回调
@override
void didChangeAccessibilityFeatures() {
print('didChangeAccessibilityFeatures');
}
// 应用程序请求退出时调用
// 在这里执行退出前的清理工作
@override
Future<AppExitResponse> didRequestAppExit() {
return super.didRequestAppExit();
}
}
class TripApp extends StatefulWidget {
const TripApp({super.key});
@override
State<TripApp> createState() => _TripAppState();
}
class _TripAppState extends State<TripApp> {
@override
Widget build(BuildContext context) {
ScreenAdaptHelper.init(context);
return MaterialApp(
title: 'trip flutter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.white),
useMaterial3: false,
),
//默认为launcher,App启动的页面
initialRoute: RoutersManager.launcher,
/// 注册路由表
routes: RoutersManager.registerRouter(context),
/// 动态路由,如果有些页面不在路由表中,则使用此方法进行跳转
onGenerateRoute: (RouteSettings settings) =>
RoutersManager.generateRoute(settings),
);
}
}
路由管理
class RoutersManager {
static const String login = "/login";
static const String register = "/register";
static const String home = "/home";
static const String launcher = "/launcher";
static const String main = "/man";
.....此处省略一万字.........
static Map<String, WidgetBuilder> registerRouter(BuildContext context) {
return {
launcher: (context) => const LauncherPage(),
home: (context) => const HomePage(),
login: (context) => const LoginPage(),
register: (context) => const RegisterPage(),
main: (context) => const AppMainPage(),
};
}
///onGenerateRoute
///如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;
///如果路由表中没有注册,才会调用onGenerateRoute来生成路由。
static Route<dynamic>? generateRoute(RouteSettings settings) {
switch (settings.name) {
case launcher:
return MaterialPageRoute(
builder: (context) => const LauncherPage(), settings: settings);
case main:
return MaterialPageRoute(
builder: (context) => const AppMainPage(), settings: settings);
case home:
return MaterialPageRoute(
builder: (context) => const HomePage(), settings: settings);
case login:
return MaterialPageRoute(
builder: (context) => const LoginPage(), settings: settings);
case register:
return MaterialPageRoute(
builder: (context) => const RegisterPage(), settings: settings);
default:
return null;
}
}
///pop 将栈顶的页面弹出
static void back(BuildContext context) => Navigator.pop(context);
///pushReplacementNamed 跳转登陆页面 ,就是把当前路由栈栈顶的页面替换成 登陆页面
static Future<dynamic> toLogin(BuildContext context,
{Map<String, Object>? arguments}) =>
Navigator.pushNamedAndRemoveUntil(
context, login, arguments: arguments, (route) => route == null);
///pushReplacementNamed 跳转注册页面 就是把当前路由栈栈顶的页面替换成 注册页面
static Future<dynamic> toRegister(BuildContext context) =>
Navigator.pushNamed(context, register);
///若要删除所有路由,直到具有特定名称的路由为止
///pushNamedAndRemoveUntil 跳转注home页面 就是把当前路由栈Home页面之前的页面全部移除
///page 选中Page
static Future<dynamic> toMainPage(BuildContext context, {int page = 0}) {
Map<String, Object>? arguments = {"page": page};
return Navigator.pushNamedAndRemoveUntil(
context, main, arguments: arguments, ModalRoute.withName(main));
}
.....此处省略一万字.........
}