Flutter利用注解生成可自定义的路由

3,649 阅读7分钟

Repository

route_generator是什么

这是一个简单的 Flutter 路由生成库,只需要少量的代码,然后利用注解配合源代码生成,自动生成路由表,省去手工管理路由代码的烦恼。

特性

  • 自定义路由名称
  • 自定义路由动画
  • 自定义路由参数
  • 自定义路由逻辑
  • 支持从pubspec.yaml文件中的依赖包自动生成路由代码

依赖

dependencies:
  # Your other regular dependencies here
  route_annotation: ^0.2.0

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.5.0
  route_generator: ^0.1.4

生成代码

  • 单次构建

    在项目根目录中运行flutter pub run build_runner build,可以在需要时为项目生成路由代码。这会触发一次性构建,该构建遍历源文件,选择相关文件,并为它们生成必要的路由代码。虽然这很方便,但如果您不必每次在模型类中进行更改时都必须手动构建,那么你可以选择持续构建。

  • 持续构建

    在项目根目录中运行flutter pub run build_runner watch来启动watcher,它可以使我们的源代码生成过程更加方便。它会监视项目文件中的更改,并在需要时自动构建必要的文件。

route_annotation

annotation description
Router 此注解用来标志某个为 Flutter App 的类,并以此生成相应的路由代码
RoutePage 此注解用来注解一个路由页面
RouteParameter 一个用来标志页面参数的注解,只为可选参数设计。用于 RoutePage
RouteField 此注解用来标志一个完全自定义的路由,被注解的对象必须作为路由页面类静态字段
PageRouteBuilderFuntcion 这个注解用来标识一个路由页面的 RouteFactory 静态方法
RoutePageBuilderFunction 这个注解用来标识一个路由页面的 RoutePageBuilder静态方法
RouteTransitionBuilderFunction 这个注解用来标识一个路由页面的 TransitionBuilder 静态方法
RouteTransitionDurationField 这个注解用来标识一个自定义路由页面的过渡时长
router new Router()相同
page new RoutePage()相同
initialPage new RoutePage(isInitialRoute: true)相同
routeField new RouteField()相同
routeBuilder new PageRouteBuilderFuntcion()相同
pageBuilder new RoutePageBuilderFunction()相同
transitionBuilder new RouteTransitionBuilderFunction()相同
transitionDuration new RouteTransitionDurationField()相同

代码示例

定义路由 App

@router
class DemoApp extends StatefulWidget {
  @override
  _DemoAppState createState() => _DemoAppState();
}

class _DemoAppState extends State<DemoApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: "/",
      onGenerateRoute: onGenerateRoute,
    );
  }
}

定义路由页面

@initialPage
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

定义路由页面参数

  • 对于单个参数

    @RoutePage(params: [RouteParameter("title")])
    class OneArgumentPage extends StatelessWidget {
      final String title;
    
      const OneArgumentPage({Key key, this.title}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container();
      }
    }
    

    导航

    Navigator.of(context).pushNamed(
      ROUTE_ONE_ARGUMENT_PAGE,
      arguments: "title is empty",
    );
    

    注意事项:

    对于单个参数的路由,利用Navigator进行导航的时候arguments即为原始参数。

  • 对于多个参数

    @RoutePage(params: [RouteParameter("title"), RouteParameter("subTitle")])
    class TwoArgumentPage extends StatelessWidget {
      final String title;
      final String subTitle;
    
      TwoArgumentPage({this.title, Key key, this.subTitle}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold();
      }
    }
    

    导航

    Navigator.of(context).pushNamed(
      ROUTE_TWO_ARGUMENT_PAGE,
      arguments: {
        "title": _titleController.text.isNotEmpty
            ? _titleController.text
            : "title is empty",
        "subTitle": _subTitleController.text.isNotEmpty
            ? _subTitleController.text
            : "sub title is empty",
      },
    );
    

    注意事项:

    对于多个参数的路由,利用Navigator进行导航的时候arguments必须为Map<string,dynamic>

如果你不需要自定义路由,以下部分,你可以什么都不用添加,就让route_generator为你自动生成相关代码吧!

自定义路由(优先级:3)

这种方法自定义路由的优先级最高,如果同时存在多种自定义路由选择,该种方案最先被选择。

@page
class CustomRoutePage extends StatelessWidget {
  @routeField
  static Map<String, RouteFactory> route = <String, RouteFactory>{
    'custom_route': (RouteSettings settings) =>
        MaterialPageRoute(builder: (BuildContext context) => CustomRoutePage()),
    'alias_route': (RouteSettings settings) => PageRouteBuilder(
          pageBuilder: (BuildContext context, Animation animation,
                  Animation secondaryAnimation) =>
              CustomRoutePage(),
        ),
  };

  ...

}

它会生成如下代码:

Map<String, RouteFactory> _customRoutePage = CustomRoutePage.route;

自定义路由(优先级:2)

这种方法自定义路由的优先级较低,如果同时存在多种自定义路由选择,则按优先级从大到小选择。

@page
class CustomRoutePage extends StatelessWidget {
  @pageBuilder
  static Route buildPageRoute(RouteSettings settings) => PageRouteBuilder(
        pageBuilder: (BuildContext context, Animation animation,
                Animation secondaryAnimation) =>
            CustomRoutePage(),
      );

  ...

}

它会生成如下代码:

Map<String, RouteFactory> _customRoutePage = <String, RouteFactory>{
  'custom_route_page': CustomRoutePage.buildPageRoute,
};

自定义路由(优先级:1)

这种方法自定义路由的优先级最低,如果同时存在多种自定义路由选择,则按优先级从大到小选择。

@page
class CustomRoutePage extends StatelessWidget {
  // pageBuilder注解表明这个方法用来定义如何返回RoutePage
  // 它是可选的
  @pageBuilder
  static Widget buildPage(BuildContext context, Animation animation,
          Animation secondaryAnimation, RouteSettings settings) =>
      CustomRoutePage();

  // transitionBuilder注解表明这个方法用来定义如何应用动画过渡
  // 它是可选的
  @transitionBuilder
  static Widget buildTransitions(
          BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
          Widget child,
          RouteSettings settings) =>
      child;

  // transitionDuration注解表明这个字段用来定义页面过渡时常长,默认值为300 milliseconds
  // 它是可选的
  @transitionDuration
  static Duration transitionDurations = Duration(milliseconds: 400);

  ...

}

它会生成如下代码:

Map<String, RouteFactory> _customRoutePage = <String, RouteFactory>{
  'custom_route_page': (RouteSettings settings) => PageRouteBuilder(
        pageBuilder: (context, animation, secondaryAnimation) =>
            CustomRoutePage.buildPage(
                context, animation, secondaryAnimation, settings),
        transitionsBuilder: (context, animation, secondaryAnimation, child) =>
            CustomRoutePage.buildTransitions(
                context, animation, secondaryAnimation, child, settings),
        transitionDuration: CustomRoutePage.transitionDurations,
      ),
};

从pubspec.yaml文件中的依赖包自动生成路由代码

  1. 在需要支持从pubspec.yaml文件中的依赖包自动生成路由代码的项目根目录下,新建build.yaml文件,如果已经存在,则跳过这一步。

  2. 在文件中添加以下内容:

    # If you are sure that you only run `flutter pub run build_runner build`,
    # and don't run `flutter pub run build_runner watch`, then you can enable
    # the following comment out content.
    # targets:
    #   $default:
    #     builders:
    #       route_generator|route_collector:
    #         enabled: false
    
    # If you also want to enable source code generation for the packages of
    # dependencies in the pubspec.yaml, I think the following is what you need.
    builders:
      route_collector_all_packages:
        import: 'package:route_generator/builder.dart'
        builder_factories: ['routeCollectorAllPackages']
        build_extensions: { '.dart': ['.collector_all_packages.dart'] }
        auto_apply: all_packages
        runs_before: ["route_generator|route_builder"]
        build_to: cache
    

    注意相同key部分请合并。

  3. 重新运行build_runner command即可

获取更详细信息,请参阅example

关于build_runner watch模式下的问题

  • 需要了解的是:pubspec.yaml dependencies packages 不支持watch模式持续生成路由代码(第一次生成依然是有效的),但是你任然可以在当前的application启用watch模式。后期考虑支持。

  • 由于BuildStep不支持同一文件的不同输出,即对于每一个文件,它的输出文件是限定了的,所以watch模式下,如果你修改了注解信息,那么你可能需要使Route注解所在的文件刷新一次(必须使文件出现改动,并且保存,例如添加空行),才会重新生成xxx.route.dart。正在尽力解决,目前方案需要手动刷新一次,如果大家有更好的方案,欢迎提出。

注意事项

  • 只允许有一个initalRoute
  • initalRoute会忽略自定义路由名,但会生成名为ROUTE_HOME的路由名称常量。
  • 所有自定义路由method或getter必须定义在路由所在类,且必须为static所修饰的和非私有的。

最终生成代码

最终生成的文件名为FILENAME.route.dart 其中FILENAME是被Router注解的App类所在的文件名。

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// RouteGenerator
// **************************************************************************

import 'package:flutter/material.dart';
import 'package:example_library/example_library.dart';
import 'package:example/custom_route_name_page.dart';
import 'package:example/custom_route_page.dart';
import 'package:example/home_page.dart';
import 'package:example/one_arguement_page.dart';
import 'package:example/two_arguement_page.dart';
import 'package:example/second_page.dart';

const ROUTE_LIBRARY_PAGE = 'library_page';
const ROUTE_CUSTOM = 'custom';
const ROUTE_CUSTOM_ROUTE_PAGE = 'custom_route_page';
const ROUTE_HOME = '/';
const ROUTE_ONE_ARGUMENT_PAGE = 'one_argument_page';
const ROUTE_TWO_ARGUMENT_PAGE = 'two_argument_page';
const ROUTE_SECOND_PAGE = 'second_page';

RouteFactory onGenerateRoute = (settings) => Map.fromEntries([
      ..._libraryPage.entries,
      ..._custom.entries,
      ..._customRoutePage.entries,
      ..._home.entries,
      ..._oneArgumentPage.entries,
      ..._twoArgumentPage.entries,
      ..._secondPage.entries,
    ])[settings.name](settings);

Map<String, RouteFactory> _libraryPage = <String, RouteFactory>{
  'library_page': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => LibraryPage(),
      ),
};
Map<String, RouteFactory> _custom = <String, RouteFactory>{
  'custom': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => CustomRoutePageName(),
      ),
};
Map<String, RouteFactory> _customRoutePage = CustomRoutePage.route;
Map<String, RouteFactory> _home = <String, RouteFactory>{
  '/': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => HomePage(),
      ),
};
Map<String, RouteFactory> _oneArgumentPage = <String, RouteFactory>{
  'one_argument_page': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) =>
            OneArgumentPage(title: settings.arguments),
      ),
};
Map<String, RouteFactory> _twoArgumentPage = <String, RouteFactory>{
  'two_argument_page': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) {
          final arguments = settings.arguments as Map<String, dynamic>;
          return TwoArgumentPage(
            title: arguments['title'],
            subTitle: arguments['subTitle'],
          );
        },
      ),
};
Map<String, RouteFactory> _secondPage = <String, RouteFactory>{
  'second_page': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => SecondPage(),
      ),
};

常见问题

  • 没有生成路由文件

    请检查是否添加了Router注解

  • 路由生成不完整

    请尝试运行以下命令:

    • flutter pub run build_runner clean
    • flutter pub run build_runner build

Example

获取更详细信息,请参阅example