Flutter利用註解生成可自定義的路由

microtears發表於2019-08-06

Repository

2019/08/09 更新

從pubspec.yaml檔案中的依賴包自動生成路由程式碼

感謝@法的空間網友的建議,新的版本支援從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。正在盡力解決,目前方案需要手動重新整理一次,如果大家有更好的方案,歡迎提出。

route_generator是什麼

這是一個簡單的 Flutter 路由生成庫,只需要少量的程式碼,然後利用註解配合原始碼生成,自動生成路由表,省去手工管理路由程式碼的煩惱。

特性

  • 自定義路由名稱
  • 自定義路由動畫
  • 自定義路由引數
  • 自定義路由邏輯
  • 支援從pubspec.yaml檔案中的依賴包自動生成路由程式碼

依賴

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

dev_dependencies:
  # Your other dev_dependencies here
  build_runner: ^1.5.0
  route_generator: ^0.1.2
複製程式碼

生成程式碼

  • 單次構建

    在專案根目錄中執行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 這個註解用來標識一個自定義路由頁面的過渡時長

程式碼示例

定義路由 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,
    );
  }
}
複製程式碼

定義路由頁面

// isInitialRoute為true表示它將作為initial page
@RoutePage(isInitialRoute: true)
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)

這種方法自定義路由的優先順序最高,如果同時存在多種自定義路由選擇,該種方案最先被選擇。

@RoutePage()
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)

這種方法自定義路由的優先順序較低,如果同時存在多種自定義路由選擇,則按優先順序從大到小選擇。

@RoutePage()
class CustomRoutePage extends StatelessWidget {
  @PageRouteBuilderFuntcion()
  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)

這種方法自定義路由的優先順序最低,如果同時存在多種自定義路由選擇,則按優先順序從大到小選擇。

@RoutePage()
class CustomRoutePage extends StatelessWidget {
  // RoutePageBuilderFunction註解表明這個方法用來定義如何返回RoutePage
  // 它是可選的
  @RoutePageBuilderFunction()
  static Widget buildPage(BuildContext context, Animation animation,
          Animation secondaryAnimation, RouteSettings settings) =>
      CustomRoutePage();

  // RouteTransitionBuilderFunction註解表明這個方法用來定義如何應用動畫過渡
  // 它是可選的
  @RouteTransitionBuilderFunction()
  static Widget buildTransitions(
          BuildContext context,
          Animation<double> animation,
          Animation<double> secondaryAnimation,
          Widget child,
          RouteSettings settings) =>
      child;

  // RouteTransitionDurationField註解表明這個欄位用來定義頁面過渡時常長,預設值為300 milliseconds
  // 它是可選的
  @RouteTransitionDurationField()
  static Duration transitionDuration = Duration(milliseconds: 400);

  ...

}
複製程式碼

它會生成如下程式碼:

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

注意事項

  • 只允許有一個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 'home_page.dart';
import 'custom_route_page.dart';
import 'custom_route_name_page.dart';
import 'second_page.dart';
import 'one_arguement_page.dart';
import 'two_arguement_page.dart';

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

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

Map<String, RouteFactory> _home = <String, RouteFactory>{
  '/': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => HomePage(),
      ),
};
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.transitionDuration,
      ),
};
Map<String, RouteFactory> _custom = <String, RouteFactory>{
  'custom': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => CustomRoutePageName(),
      ),
};
Map<String, RouteFactory> _secondPage = <String, RouteFactory>{
  'second_page': (RouteSettings settings) => MaterialPageRoute(
        builder: (BuildContext context) => SecondPage(),
      ),
};
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) => TwoArgumentPage(
              title: (settings.arguments as Map<String, dynamic>)['title'],
              subTitle:
                  (settings.arguments as Map<String, dynamic>)['subTitle'],
            ),
      ),
};

複製程式碼

常見問題

  • 沒有生成路由檔案

    請檢查是否新增了Router註解

Example

獲取更詳細資訊,請參閱example

相關文章