原生路由
初始化
在MyApp的build方法中添加常规路由处理方法onGenerateRoute,未知路由处理方法onUnknownRoute,以及定义初始路由initialRoute。
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: 'splash',
onGenerateRoute: onGenerateRoute,
onUnknownRoute: onUnknownRoute,
);
}
}
//固定写法
var onGenerateRoute = (RouteSettings settings) {
final String name = settings.name ??"404";
final Function pageContentBuilder = routes[name] as Function;
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route =
MaterialPageRoute(builder: (context) => pageContentBuilder(context));
return route;
}
}
};
// 未知路由
var onUnknownRoute = (RouteSettings settings) {
final String name = "404";
final Function pageContentBuilder = routes[name] as Function;
if (pageContentBuilder != null) {
final Route route =
MaterialPageRoute(builder: (context) => pageContentBuilder(context));
return route;
}
};
final routes = {
'404': (context) => NotFoundPage(),/// 404 notfound
/// 往这里加
};
添加了onGenerateRoute之后,Flutter的路由优先执行onGenerateRoute方法,initialRoute配置的初始路由也是通过onGenerateRoute去查找。当代码传递的路由名称在routes中找不到时,就会执行onUnknownRoute方法,跳转到自定义未知路由。
跳转
使用Navigator.pushNamed 跳转到指定的路由。
Navigator.pushNamed(context, 'RouteName',arguments: 携带的数据);
这是Navigator.pushNamed方法的源码:
@optionalTypeArgs
static Future<T?> pushNamed<T extends Object?>(
BuildContext context,
String routeName, {
Object? arguments,
}) {
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
}
返回的是一个 Future
Navigator.pushNamed(context, 'RouteName',arguments: 携带的数据)
.then((value) {
// todo something
});
关闭页面
使用Navigator.pop来关闭当前页面。
Navigator.pop(context);
这是Navigator.pop方法的源码:
@optionalTypeArgs
static void pop<T extends Object?>(BuildContext context, [ T? result ]) {
Navigator.of(context).pop<T>(result);
}
若果需要带参返回,则将参数赋值给result,由Navigator.pushNamed().then接收数据。如:
Navigator.pop(context,data);
其他方法
- popAndPushNamed 关闭当前页并根据名称跳转到指定页
- pushReplacementNamed 根据名称更换当前路由堆栈
- pushNamedAndRemoveUntil 根据名称跳转指定页并移除之前的路由堆栈(将跳转到的页面作为根路由)
- push 跳转到指定页
- pushReplacement 更换当前路由堆栈
- pushAndRemoveUntil 跳转指定页并移除之前的路由堆栈(将跳转到的页面作为根路由)
- replace 替换路由
- replaceRouteBelow 更换路由路线
- popUntil 关闭所有路由
- removeRoute 移除路由
- removeRouteBelow 移除路由路线
- canPop 判断当前页面是否可关闭
GetX(推荐使用)※
getx是一款功能强大的插件,路由管理是其中一项功能。
初始化
在pubspec.yaml中引入getx,当前使用的版本为4.3.4。
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
get: ^4.3.4 # 路由和状态管理
在main.dart的MyApp中使用GetMaterialApp替换原有的MaterialApp:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
跳转
使用 Get.to(()=>Page()) (或Get.to(Page()) ,没有其他参数的情况下推荐使用前者) 实现跳转,表示从当前页面跳转到Page页面,也可以使用Get.toNamed(‘path’):
Get.to(()=>Page()) ; // Get.to(Page());
Get.toNamed('path');
使用Get.to可以在Page()中传递构造参数,也可以使用get中的arguments传参:
Get.to(Page(data:data),arguments: data);
Get.toNamed('path',arguments: data); ///Get.toNamed 不能使用构造参数传值
如果使用Get.toNamed,需要配置path解析方法getPages:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/home',
getPages: getPageList,
);
}
}
var getPageList = [GetPage(name: '/home', page: () => HomePage()),];
getPageList是个数组,按GetPage(name: ‘/home’, page: () => HomePage()),格式往里边添加即可。
相同的,Get.to方法返回一个Future
Get.to(()=>HomePage())?.then((value) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('返回值为:$value'),
),// 弹出底部提示框,显示返回值
);
});
关闭页面
关闭当前页面:
Get.back();
关闭当前页面并跳转到新页面:
Get.off(()=>Page());
Get.offNamed('path');
清除所有路由并跳转到新页面(以新页面作为根节点):
Get.offAll(()=>Page());
Get.offAllNamed('path');
其他方法
其他用法和原生相似,具体方法可在getx的GetNavigation类中查询,附上源码:
extension GetNavigation on GetInterface {
/// **Navigation.push()** shortcut.<br><br>
///
/// Pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it here
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? to<T>(
dynamic page, {
bool? opaque,
Transition? transition,
Curve? curve,
Duration? duration,
int? id,
String? routeName,
bool fullscreenDialog = false,
dynamic arguments,
Bindings? binding,
bool preventDuplicates = true,
bool? popGesture,
double Function(BuildContext context)? gestureWidth,
}) {
// var routeName = "/${page.runtimeType}";
routeName ??= "/${page.runtimeType}";
routeName = _cleanRouteName(routeName);
if (preventDuplicates && routeName == currentRoute) {
return null;
}
return global(id).currentState?.push<T>(
GetPageRoute<T>(
opaque: opaque ?? true,
page: _resolvePage(page, 'to'),
routeName: routeName,
gestureWidth: gestureWidth,
settings: RouteSettings(
name: routeName,
arguments: arguments,
),
popGesture: popGesture ?? defaultPopGesture,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
fullscreenDialog: fullscreenDialog,
binding: binding,
transitionDuration: duration ?? defaultTransitionDuration,
),
);
}
GetPageBuilder _resolvePage(dynamic page, String method) {
if (page is GetPageBuilder) {
return page;
} else if (page is Widget) {
Get.log(
'''WARNING, consider using: "Get.$method(() => Page())" instead of "Get.$method(Page())".
Using a widget function instead of a widget fully guarantees that the widget and its controllers will be removed from memory when they are no longer used.
''');
return () => page;
} else if (page is String) {
throw '''Unexpected String,
use toNamed() instead''';
} else {
throw '''Unexpected format,
you can only use widgets and widget functions here''';
}
}
/// **Navigation.pushNamed()** shortcut.<br><br>
///
/// Pushes a new named `page` to the stack.
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
///
/// Note: Always put a slash on the route ('/page1'), to avoid unnexpected errors
Future<T?>? toNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) {
if (preventDuplicates && page == currentRoute) {
return null;
}
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushNamed<T>(
page,
arguments: arguments,
);
}
/// **Navigation.pushReplacementNamed()** shortcut.<br><br>
///
/// Pop the current named `page` in the stack and push a new one in its place
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
///
/// Note: Always put a slash on the route ('/page1'), to avoid unnexpected errors
Future<T?>? offNamed<T>(
String page, {
dynamic arguments,
int? id,
bool preventDuplicates = true,
Map<String, String>? parameters,
}) {
if (preventDuplicates && page == currentRoute) {
return null;
}
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushReplacementNamed(
page,
arguments: arguments,
);
}
/// **Navigation.popUntil()** shortcut.<br><br>
///
/// Calls pop several times in the stack until [predicate] returns true
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
///
/// or also like this:
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the
/// dialog is closed
void until(RoutePredicate predicate, {int? id}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
return global(id).currentState?.popUntil(predicate);
}
/// **Navigation.pushAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given `page`, and then pop several pages in the stack until
/// [predicate] returns true
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// Obs: unlike other get methods, this one you need to send a function
/// that returns the widget to the page argument, like this:
/// Get.offUntil(GetPageRoute(page: () => HomePage()), predicate)
///
/// [predicate] can be used like this:
/// `Get.offUntil(page, (route) => (route as GetPageRoute).routeName == '/home')`
/// to pop routes in stack until home,
/// or also like this:
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
Future<T?>? offUntil<T>(Route<T> page, RoutePredicate predicate, {int? id}) {
// if (key.currentState.mounted) // add this if appear problems on future with route navigate
// when widget don't mounted
return global(id).currentState?.pushAndRemoveUntil<T>(page, predicate);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push the given named `page`, and then pop several pages in the stack
/// until [predicate] returns true
///
/// You can send any type of value to the other route in the [arguments].
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// [predicate] can be used like this:
/// `Get.offNamedUntil(page, ModalRoute.withName('/home'))`
/// to pop routes in stack until home,
/// or like this:
/// `Get.offNamedUntil((route) => !Get.isDialogOpen())`,
/// to make sure the dialog is closed
///
/// Note: Always put a slash on the route name ('/page1'), to avoid unexpected errors
Future<T?>? offNamedUntil<T>(
String page,
RoutePredicate predicate, {
int? id,
dynamic arguments,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
page,
predicate,
arguments: arguments,
);
}
/// **Navigation.popAndPushNamed()** shortcut.<br><br>
///
/// Pop the current named page and pushes a new `page` to the stack
/// in its place
///
/// You can send any type of value to the other route in the [arguments].
/// It is very similar to `offNamed()` but use a different approach
///
/// The `offNamed()` pop a page, and goes to the next. The
/// `offAndToNamed()` goes to the next page, and removes the previous one.
/// The route transition animation is different.
Future<T?>? offAndToNamed<T>(
String page, {
dynamic arguments,
int? id,
dynamic result,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: page, queryParameters: parameters);
page = uri.toString();
}
return global(id).currentState?.popAndPushNamed(
page,
arguments: arguments,
result: result,
);
}
/// **Navigation.removeRoute()** shortcut.<br><br>
///
/// Remove a specific [route] from the stack
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
void removeRoute(Route<dynamic> route, {int? id}) {
return global(id).currentState?.removeRoute(route);
}
/// **Navigation.pushNamedAndRemoveUntil()** shortcut.<br><br>
///
/// Push a named `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context, so you can
/// call from your business logic.
///
/// You can send any type of value to the other route in the [arguments].
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
/// or also like
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// Note: Always put a slash on the route ('/page1'), to avoid unexpected errors
Future<T?>? offAllNamed<T>(
String newRouteName, {
RoutePredicate? predicate,
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) {
if (parameters != null) {
final uri = Uri(path: newRouteName, queryParameters: parameters);
newRouteName = uri.toString();
}
return global(id).currentState?.pushNamedAndRemoveUntil<T>(
newRouteName,
predicate ?? (_) => false,
arguments: arguments,
);
}
/// Returns true if a Snackbar, Dialog or BottomSheet is currently OPEN
bool get isOverlaysOpen =>
(isSnackbarOpen! || isDialogOpen! || isBottomSheetOpen!);
/// Returns true if there is no Snackbar, Dialog or BottomSheet open
bool get isOverlaysClosed =>
(!isSnackbarOpen! && !isDialogOpen! && !isBottomSheetOpen!);
/// **Navigation.popUntil()** shortcut.<br><br>
///
/// Pop the current page, snackbar, dialog or bottomsheet in the stack
///
/// if your set [closeOverlays] to true, Get.back() will close the
/// currently open snackbar/dialog/bottomsheet AND the current page
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// It has the advantage of not needing context, so you can call
/// from your business logic.
void back<T>({
T? result,
bool closeOverlays = false,
bool canPop = true,
int? id,
}) {
if (closeOverlays && isOverlaysOpen) {
navigator?.popUntil((route) {
return (isOverlaysClosed);
});
}
if (canPop) {
if (global(id).currentState?.canPop() == true) {
global(id).currentState?.pop<T>(result);
}
} else {
global(id).currentState?.pop<T>(result);
}
}
/// **Navigation.popUntil()** (with predicate) shortcut .<br><br>
///
/// Close as many routes as defined by [times]
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
void close(int times, [int? id]) {
if (times < 1) {
times = 1;
}
var count = 0;
var back = global(id).currentState?.popUntil((route) => count++ == times);
return back;
}
/// **Navigation.pushReplacement()** shortcut .<br><br>
///
/// Pop the current page and pushes a new `page` to the stack
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], define a Tween [curve],
/// and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it here
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? off<T>(
dynamic page, {
bool opaque = false,
Transition? transition,
Curve? curve,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Bindings? binding,
bool fullscreenDialog = false,
bool preventDuplicates = true,
Duration? duration,
double Function(BuildContext context)? gestureWidth,
}) {
routeName ??= "/${page.runtimeType.toString()}";
routeName = _cleanRouteName(routeName);
if (preventDuplicates && routeName == currentRoute) {
return null;
}
return global(id).currentState?.pushReplacement(GetPageRoute(
opaque: opaque,
gestureWidth: gestureWidth,
page: _resolvePage(page, 'off'),
binding: binding,
settings: RouteSettings(
arguments: arguments,
name: routeName,
),
routeName: routeName,
fullscreenDialog: fullscreenDialog,
popGesture: popGesture ?? defaultPopGesture,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
transitionDuration: duration ?? defaultTransitionDuration));
}
///
/// Push a `page` and pop several pages in the stack
/// until [predicate] returns true. [predicate] is optional
///
/// It has the advantage of not needing context,
/// so you can call from your business logic
///
/// You can set a custom [transition], a [curve] and a transition [duration].
///
/// You can send any type of value to the other route in the [arguments].
///
/// Just like native routing in Flutter, you can push a route
/// as a [fullscreenDialog],
///
/// [predicate] can be used like this:
/// `Get.until((route) => Get.currentRoute == '/home')`so when you get to home page,
/// or also like
/// `Get.until((route) => !Get.isDialogOpen())`, to make sure the dialog
/// is closed
///
/// [id] is for when you are using nested navigation,
/// as explained in documentation
///
/// If you want the same behavior of ios that pops a route when the user drag,
/// you can set [popGesture] to true
///
/// If you're using the [Bindings] api, you must define it here
///
/// By default, GetX will prevent you from push a route that you already in,
/// if you want to push anyway, set [preventDuplicates] to false
Future<T?>? offAll<T>(
dynamic page, {
RoutePredicate? predicate,
bool opaque = false,
bool? popGesture,
int? id,
String? routeName,
dynamic arguments,
Bindings? binding,
bool fullscreenDialog = false,
Transition? transition,
Curve? curve,
Duration? duration,
double Function(BuildContext context)? gestureWidth,
}) {
routeName ??= "/${page.runtimeType.toString()}";
routeName = _cleanRouteName(routeName);
return global(id).currentState?.pushAndRemoveUntil<T>(
GetPageRoute<T>(
opaque: opaque,
popGesture: popGesture ?? defaultPopGesture,
page: _resolvePage(page, 'offAll'),
binding: binding,
gestureWidth: gestureWidth,
settings: RouteSettings(
name: routeName,
arguments: arguments,
),
fullscreenDialog: fullscreenDialog,
routeName: routeName,
transition: transition ?? defaultTransition,
curve: curve ?? defaultTransitionCurve,
transitionDuration: duration ?? defaultTransitionDuration,
),
predicate ?? (route) => false);
}
/// 省略部分代码
}