Flutter路由注解插件ff_annotation_route的使用
Flutter路由注解插件ff_annotation_route的使用
描述
ff_annotation_route
是一个用于快速生成路由映射的插件。通过注解的方式,你可以更方便地管理路由。
使用
添加依赖包到依赖项
在项目的 pubspec.yaml
文件中添加以下依赖:
dependencies:
# 添加此包以支持路由注解
ff_annotation_route_core: any
# 仅在项目中添加此包
ff_annotation_route_library: any
然后运行以下命令来获取这些包:
flutter packages get
添加注解
空构造函数
import 'package:ff_annotation_route/ff_annotation_route.dart';
[@FFRoute](/user/FFRoute)(
name: "fluttercandies://mainpage",
routeName: "MainPage",
)
class MainPage extends StatelessWidget {
// ...
}
带参数的构造函数
import 'package:ff_annotation_route/ff_annotation_route.dart';
@FFAutoImport('hide TestMode2')
import 'package:example1/src/model/test_model.dart';
@FFAutoImport()
import 'package:example1/src/model/test_model1.dart' hide TestMode3;
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
[@FFRoute](/user/FFRoute)(
name: 'flutterCandies://testPageE',
routeName: 'testPageE',
description: 'Show how to push new page with arguments(class)',
argumentImports: [
'import \'package:example1/src/model/test_model.dart\';',
'import \'package:example1/src/model/test_model1.dart\';',
],
exts: {
'group': 'Complex',
'order': 1,
},
)
class TestPageE extends StatelessWidget {
const TestPageE({
this.testMode = const TestMode(
id: 2,
isTest: false,
),
this.testMode1,
});
factory TestPageE.deafult() => TestPageE(
testMode: TestMode.deafult(),
);
factory TestPageE.required({[@required](/user/required) TestMode testMode}) => TestPageE(
testMode: testMode,
);
final TestMode testMode;
final TestMode1 testMode1;
}
FFRoute参数说明
参数名 | 描述 | 默认值 |
---|---|---|
name | 路由名称(例如 “/settings”) | 必填 |
showStatusBar | 是否显示状态栏 | true |
routeName | 路由名称以跟踪页面 | ‘’ |
pageRouteType | 页面路由类型(material, cupertino, transparent) | - |
description | 路由描述 | ‘’ |
exts | 扩展参数 | - |
argumentImports | 参数导入 | - |
codes | 支持无法写入注解的内容 | - |
生成路由文件
环境设置
将 dart
的 bin 目录添加到你的 $PATH
中:
cache\dart-sdk\bin
激活插件
运行以下命令激活插件:
dart pub global activate ff_annotation_route
执行命令
进入项目根目录并执行命令:
ff_route <command> [arguments]
命令参数
可用命令:
-h, --[no-]help 帮助使用 -p, --path Flutter项目根路径(默认为".") -n, --name 路由常量类名称(默认为"Routes") -o, --output 主项目路由文件和帮助文件的路径(相对于lib目录) -g, --git 扫描git lib(你应该指定包名并用逗号分隔多个包名) –exclude-packages 排除给定包进行扫描 –routes-file-output 路由文件的路径(相对于lib目录) –const-ignore 忽略某些路由常量的正则表达式 –[no-]package 这是一个包 –[no-]super-arguments 是否生成页面参数帮助类 -s, --[no-]save 是否保存参数到本地 它将在没有参数的情况下运行"ff_route"时执行本地参数 –[no-]null-safety 启用null-safety(默认开启) –[no-]arguments-case-sensitive 参数是否区分大小写(默认开启) –[no-]fast-mode 快速模式:仅基于单个dart文件进行分析,速度快。 非快速模式:基于整个包和sdk进行分析,支持超级参数,并自动添加参数引用导入。 (默认开启)
### Navigator 1.0
你可以在[这里](https://github.com/fluttercandies/ff_annotation_route/tree/master/example)查看完整的演示。
#### Main.dart
```dart
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/material.dart';
import 'example_route.dart';
import 'example_routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'ff_annotation_route demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.fluttercandiesMainpage,
onGenerateRoute: (RouteSettings settings) {
return onGenerateRoute(
settings: settings,
getRouteSettings: getRouteSettings,
routeSettingsWrapper: (FFRouteSettings ffRouteSettings) {
if (ffRouteSettings.name == Routes.fluttercandiesMainpage ||
ffRouteSettings.name == Routes.fluttercandiesDemogrouppage.name) {
return ffRouteSettings;
}
return ffRouteSettings.copyWith(
widget: CommonWidget(
child: ffRouteSettings.widget,
title: ffRouteSettings.routeName,
));
},
);
},
);
}
}
Push
通过名称推送
Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);
带参数的推送
Navigator.pushNamed(
context,
Routes.flutterCandiesTestPageE,
arguments: <String, dynamic>{
constructorName: 'required',
'testMode': const TestMode(
id: 100,
isTest: true,
),
},
);
开启–super-arguments
Navigator.pushNamed(
context,
Routes.flutterCandiesTestPageE.name,
arguments: Routes.flutterCandiesTestPageE.requiredC(
testMode: const TestMode(
id: 100,
isTest: true,
),
),
);
Navigator 2.0
你可以在这里查看完整的演示。
Main.dart
import 'dart:convert';
import 'package:example1/src/model/test_model.dart';
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'example1_route.dart';
import 'example1_routes.dart';
void main() {
FFConvert.convert = <T>(dynamic value) {
if (value == null) {
return null;
}
final dynamic output = json.decode(value.toString());
if (<int>[] is T && output is List<dynamic>) {
return output.map<int>((dynamic e) => asT<int>(e)).toList() as T;
} else if (<String, String>{} is T && output is Map<dynamic, dynamic>) {
return output.map<String, String>((dynamic key, dynamic value) =>
MapEntry<String, String>(key.toString(), value.toString())) as T;
} else if (const TestMode() is T && output is Map<dynamic, dynamic>) {
return TestMode.fromJson(output) as T;
}
return json.decode(value.toString()) as T;
};
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final FFRouteInformationParser _ffRouteInformationParser =
FFRouteInformationParser();
final FFRouterDelegate _ffRouterDelegate = FFRouterDelegate(
getRouteSettings: getRouteSettings,
pageWrapper: <T>(FFPage<T> ffPage) {
return ffPage.copyWith(
widget: ffPage.name == Routes.fluttercandiesMainpage ||
ffPage.name == Routes.fluttercandiesDemogrouppage.name
? ffPage.widget
: CommonWidget(
child: ffPage.widget,
routeName: ffPage.routeName,
),
);
},
);
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'ff_annotation_route demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
routeInformationProvider: PlatformRouteInformationProvider(
initialRouteInformation: const RouteInformation(
location: Routes.fluttercandiesMainpage,
),
),
routeInformationParser: _ffRouteInformationParser,
routerDelegate: _ffRouterDelegate,
);
}
}
FFRouteInformationParser
它在Web上工作,当你在浏览器中输入或报告到浏览器时。这是一个由[Router]小部件使用的委托,将路由信息解析为[RouteSettings]配置。
FFRouterDelegate
这是一个由[Router]小部件使用的委托,构建和配置一个导航小部件。
推送名称
FFRouterDelegate.of(context).pushNamed<void>(
Routes.flutterCandiesTestPageA,
);
带参数的推送
FFRouterDelegate.of(context).pushNamed<void>(
Routes.flutterCandiesTestPageF.name,
arguments: Routes.flutterCandiesTestPageF.d(
<int>[1, 2, 3],
map: <String, String>{'ddd': 'dddd'},
testMode: const TestMode(id: 1, isTest: true),
),
);
开启–super-arguments
FFRouterDelegate.of(context).pushNamed<void>(
Routes.flutterCandiesTestPageF.name,
arguments: <String, dynamic>{
'list': <int>[1, 2, 3],
'map': <String, String>{'ddd': 'dddd'},
'testMode': const TestMode(id: 1, isTest: true),
}
)
Getx
Getx支持,你只需要将FFRouteSettings
转换为GetPageRoute
。
如何使用
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'ff_annotation_route demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.fluttercandiesMainpage.name,
onGenerateRoute: (RouteSettings settings) {
FFRouteSettings ffRouteSettings = getRouteSettings(
name: settings.name!,
arguments: settings.arguments as Map<String, dynamic>?,
notFoundPageBuilder: () => Scaffold(
appBar: AppBar(),
body: const Center(
child: Text('not find page'),
),
),
);
Bindings? binding;
if (ffRouteSettings.codes != null) {
binding = ffRouteSettings.codes!['binding'] as Bindings?;
}
Transition? transition;
bool opaque = true;
if (ffRouteSettings.pageRouteType != null) {
switch (ffRouteSettings.pageRouteType) {
case PageRouteType.cupertino:
transition = Transition.cupertino;
break;
case PageRouteType.material:
transition = Transition.downToUp;
break;
case PageRouteType.transparent:
opaque = false;
break;
default:
}
}
return GetPageRoute(
binding: binding,
opaque: opaque,
settings: ffRouteSettings,
transition: transition,
page: () => ffRouteSettings.builder(),
);
},
);
}
}
如何设置GetPageRoute的参数
例如: 'Bindings’不是const类,因此不能写在注解中,但你可以按以下代码设置:
- 在
codes
中定义 - 在
argumentImports
中添加导入URL - 在
onGenerateRoute
中获取
[@FFRoute](/user/FFRoute)(
name: "/BindingsPage",
routeName: 'BindingsPage',
description: 'how to use Bindings with Annotation.',
codes: <String, String>{
'binding': 'Bindings1()',
},
argumentImports: <String>[
'import \'package:example_getx/src/bindings/bindings1.dart\';'
],
)
onGenerateRoute: (RouteSettings settings) {
FFRouteSettings ffRouteSettings = getRouteSettings(
name: settings.name!,
arguments: settings.arguments as Map<String, dynamic>?,
notFoundPageBuilder: () => Scaffold(
appBar: AppBar(),
body: const Center(
child: Text('not find page'),
),
),
);
Bindings? binding;
if (ffRouteSettings.codes != null) {
binding = ffRouteSettings.codes!['binding'] as Bindings?;
}
Transition? transition;
bool opaque = true;
if (ffRouteSettings.pageRouteType != null) {
switch (ffRouteSettings.pageRouteType) {
case PageRouteType.cupertino:
transition = Transition.cupertino;
break;
case PageRouteType.material:
transition = Transition.downToUp;
break;
case PageRouteType.transparent:
opaque = false;
break;
default:
}
}
return GetPageRoute(
binding: binding,
opaque: opaque,
settings: ffRouteSettings,
transition: transition,
page: () => ffRouteSettings.builder(),
);
},
功能化组件
如何与功能化组件一起使用?
[@swidget](/user/swidget)
[@FFRoute](/user/FFRoute)(
name: 'flutterCandies://func1',
routeName: 'test-func-1',
)
Widget func1(
int a,
String? b, {
bool? c,
required double d,
}) {
return Container();
}
简单的代码可以在这里找到:这里。
代码提示
你可以使用路由作为Routes.flutterCandiesTestPageE
,并在IDE中查看代码提示。
默认
/// 'This is test page E.'
///
/// [name] : 'flutterCandies://testPageE'
///
/// [routeName] : 'testPageE'
///
/// [description] : 'This is test page E.'
///
/// [constructors] :
///
/// TestPageE : [TestMode testMode, TestMode1 testMode1]
///
/// TestPageE.deafult : []
///
/// TestPageE.required : [TestMode(required) testMode]
///
/// [exts] : {group: Complex, order: 1}
static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';
开启–super-arguments
/// 'This is test page E.'
///
/// [name] : 'flutterCandies://testPageE'
///
/// [routeName] : 'testPageE'
///
/// [description] : 'This is test page E.'
///
/// [constructors] :
///
/// TestPageE : [TestMode testMode, TestMode1 testMode1]
///
/// TestPageE.test : []
///
/// TestPageE.requiredC : [TestMode(required) testMode]
///
/// [exts] : {group: Complex, order: 1}
static const _FlutterCandiesTestPageE flutterCandiesTestPageE = _FlutterCandiesTestPageE();
class _FlutterCandiesTestPageE {
const _FlutterCandiesTestPageE();
String get name => 'flutterCandies://testPageE';
Map<String, dynamic> d({
TestMode testMode = const TestMode(id: 2, isTest: false),
TestMode1 testMode1}) =>
<String, dynamic>{
'testMode': testMode,
'testMode1': testMode1,
};
Map<String, dynamic> test() => const <String, dynamic>{
'constructorName': 'test',
};
Map<String, dynamic> requiredC({[@required](/user/required) TestMode testMode}) =>
<String, dynamic>{
'testMode': testMode,
'constructorName': 'requiredC',
};
[@override](/user/override)
String toString() => name;
}
我可以不用它,但你必须有它
拦截器
路由拦截器
实现RouteInterceptor
来根据特定场景拦截路由转换。
class LoginInterceptor extends RouteInterceptor {
const LoginInterceptor();
[@override](/user/override)
Future<RouteInterceptResult> intercept(
String routeName, {
Object? arguments,
}) async {
if (!User().hasLogin) {
return RouteInterceptResult.complete(
routeName: Routes.fluttercandiesLoginPage.name,
);
}
return RouteInterceptResult.next(
routeName: routeName,
arguments: arguments,
);
}
}
添加拦截器注解
在页面上添加拦截器注解以进行路由拦截。
[@FFRoute](/user/FFRoute)(
name: 'fluttercandies://PageA',
routeName: 'PageA',
description: 'PageA',
interceptors: [
LoginInterceptor(),
],
)
class PageA extends StatefulWidget {
const PageA({Key? key}) : super(key: key);
[@override](/user/override)
State<PageA> createState() => _PageAState();
}
生成映射
执行ff_route
生成拦截器映射。
/// The routeInterceptors auto generated by https://github.com/fluttercandies/ff_annotation_route
const Map<String, List<RouteInterceptor>> routeInterceptors = {
'fluttercandies://PageA': [LoginInterceptor()],
'fluttercandies://PageB': [
LoginInterceptor(),
PermissionInterceptor()
],
};
完整配置
void main() {
RouteInterceptorManager().addAllRouteInterceptors(routeInterceptors);
runApp(const MyApp());
}
全局拦截器
如果你不想在注解中添加拦截器,可以选择使用全局拦截器。
实现RouteInterceptor
根据具体场景编写逻辑。
class GlobalLoginInterceptor extends RouteInterceptor {
const GlobalLoginInterceptor();
[@override](/user/override)
Future<RouteInterceptResult> intercept(String routeName,
{Object? arguments}) async {
if (routeName == Routes.fluttercandiesPageB.name ||
routeName == Routes.fluttercandiesPageA.name) {
if (!User().hasLogin) {
return RouteInterceptResult.complete(
routeName: Routes.fluttercandiesLoginPage.name,
);
}
}
return RouteInterceptResult.next(
routeName: routeName,
arguments: arguments,
);
}
}
完整配置
void main() {
RouteInterceptorManager().addGlobalInterceptors([
const GlobalLoginInterceptor(),
const GlobalPermissionInterceptor(),
]);
runApp(const MyApp());
}
推送路由
- 使用
NavigatorWithInterceptorExtension
扩展调用带WithInterceptor
的方法。
Navigator.of(context).pushNamedWithInterceptor(
Routes.fluttercandiesPageA.name,
);
- 调用
NavigatorWithInterceptor
的静态方法。
NavigatorWithInterceptor.pushNamed(
context,
Routes.fluttercandiesPageB.name,
);
生命周期
RouteLifecycleState
通过继承RouteLifecycleState
,你可以轻松检测页面的各种状态。
class _PageBState extends RouteLifecycleState<PageB> {
[@override](/user/override)
void onForeground() {
print('PageB onForeground');
}
[@override](/user/override)
void onBackground() {
print('PageB onBackground');
}
[@override](/user/override)
void onPageShow() {
print('PageB onPageShow');
}
[@override](/user/override)
void onPageHide() {
print('PageB onPageHide');
}
[@override](/user/override)
void onRouteShow() {
print('onRouteShow');
}
[@override](/user/override)
void onRouteHide() {
print('onRouteHide');
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page B'),
),
body: GestureDetector(
onTap: () {},
child: const Center(
child: Text('This is Page B'),
),
),
);
}
}
ExtendedRouteObserver
ExtendedRouteObserver
是一个实用类,它扩展了Flutter内置的RouteObserver
的功能。它允许更高级别的路由管理和跟踪。
关键特性:
- 跟踪导航堆栈中的所有活动路由。
- 提供访问最顶层路由的方法。
- 允许检查特定路由是否存在于堆栈中。
- 允许通过名称检索路由。
- 通知订阅者当路由被添加或移除时。
- 支持自定义操作当路由被添加或移除时。
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [ExtendedRouteObserver()],
);
}
全局导航器
GlobalNavigator
类是一个用于管理全局导航动作的工具类。它提供从应用程序任何位置访问Navigator
和BuildContext
的便捷方式。
虽然在某些情况下可以通过全局navigatorKey
直接访问Navigator
或context
,但这通常不推荐,尤其是在Flutter推荐的模式下(例如通过context
访问)工作良好时。
这种做法可能会引入一些潜在问题:
- 违背了Flutter的设计哲学:Flutter的设计基于局部导航和状态管理。绕过
context
的全局方法可能导致状态管理混乱,使代码难以维护。 - 可能导致性能问题:全局访问
context
可能会绕过Flutter的优化机制,因为Flutter依赖于上下文树的结构来进行高效的UI更新。 - 维护性差:依赖全局导航会使代码更难理解和维护,尤其是随着应用的增长。可能难以追踪导航流程和状态。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: GlobalNavigator.navigatorKey,
home: HomeScreen(),
);
}
}
GlobalNavigator.navigator?.push(
MaterialPageRoute(builder: (context) => SecondScreen()),
);
showDialog(
context: GlobalNavigator.context!,
builder: (b) {
return AlertDialog(
title: const Text('Permission Denied'),
content:
Text('You do not have permission to access this page.'),
actions: [
TextButton(
onPressed: () {
GlobalNavigator.navigator?.pop();
},
child: const Text('OK'),
),
],
);
},
);
更多关于Flutter路由注解插件ff_annotation_route的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter路由注解插件ff_annotation_route的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用ff_annotation_route
插件的示例。这个插件允许你通过注解的方式定义路由,从而简化路由管理。
1. 添加依赖
首先,在你的pubspec.yaml
文件中添加ff_annotation_route
依赖:
dependencies:
flutter:
sdk: flutter
ff_annotation_route: ^最新版本号 # 请替换为最新版本号
然后运行flutter pub get
来获取依赖。
2. 配置生成代码
在项目的根目录下创建一个build.yaml
文件,并添加以下内容来配置代码生成:
targets:
$default:
builders:
ff_annotation_route:
enabled: true
3. 定义路由注解
接下来,在你的Dart文件中使用注解来定义路由。例如,假设你有两个页面:HomePage
和SecondPage
。
import 'package:flutter/material.dart';
import 'package:ff_annotation_route/ff_annotation_route.dart';
// 定义HomePage路由
@FFRoute(
name: 'homepage',
routePage: HomePage,
)
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: () {
// 导航到SecondPage
FFRouteManager.getInstance().pushNamed('secondpage');
},
child: Text('Go to Second Page'),
),
),
);
}
}
// 定义SecondPage路由
@FFRoute(
name: 'secondpage',
routePage: SecondPage,
)
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Page')),
body: Center(
child: Text('This is the Second Page'),
),
);
}
}
4. 初始化路由管理
在你的main.dart
文件中,初始化FFRouteManager
并设置初始路由。
import 'package:flutter/material.dart';
import 'package:ff_annotation_route/ff_annotation_route.dart';
import 'home_page.dart'; // 假设HomePage定义在这个文件中
void main() {
// 初始化路由管理
FFRouteManager.getInstance()
..addRoute(<String, dynamic>{
'homepage': (settings) => HomePage(), // 可以省略,因为已经通过注解定义了
});
// 启动应用
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// 使用FFRouteManager作为navigator
navigatorKey: FFRouteManager.getInstance().navigatorKey,
initialRoute: 'homepage', // 设置初始路由
onGenerateRoute: (settings) {
// 通过FFRouteManager生成路由
return FFRouteManager.getInstance().onGenerateRoute(settings);
},
);
}
}
5. 运行项目
现在你可以运行你的Flutter项目,应该会看到HomePage
,点击按钮后会导航到SecondPage
。
这个示例展示了如何使用ff_annotation_route
插件通过注解定义路由,并简化路由管理。如果你有更多的页面和复杂的路由需求,可以继续使用这种方式添加更多的路由注解。