一、简介

在Flutter应用开发过程中,多个页面的跳转需要使用路由,除了官方提供的Navigator外,我们还可以使用一些第三方路由框架来实现页面的管理和导航,如Fluro、Frouter等。不过,今天要给大家介绍的是另一款路由框架auto_route。

auto_route是一个设计精简、低耦合的路由框架,支持自动生成路由代码、动态添加路由、以及路由的参数传递等功能。相比其他的路由框架,auto_route的使用也更加简洁。

二、基本使用

2.1 安装插件

和其他Flutter插件的使用流程一样,使用之前需要先在项目中安装auto_route插件,安装的的脚本如下:

dependencies:              
  auto_route: [latest-version]              
dev_dependencies:              
  auto_route_generator: [latest-version]              
  build_runner:       

2.2 定义路由表

接下来,定义一个路由表的管理类,用来同意管理应用的路由,需要使用@MaterialAutoRouter注解进行标识,如下。       

@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(page: BookListPage, initial: true),              
    AutoRoute(page: BookDetailsPage),              
  ],              
)              
class $AppRouter {}

要生成路由文件的一部分而不是独立的 AppRouter 类,只需将 Part 指令添加到AppRouter 并扩展生成的私有路由器即可。

part 'app_router.gr.dart';      
@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(page: BookListPage, initial: true),              
    AutoRoute(page: BookDetailsPage),              
  ],              
)              
class AppRouter extends _$AppRouter{}

接下来,我们使用build_runner提供的命令即可生成路由代码。

//自动刷新路由表
flutter packages pub run build_runner watch       
//生成路由代码
flutter packages pub run build_runner build 

等待命令执行完成之后,即可在app_router.dart同级的目录下生成一个app_route.gr.dart文件,也是我们执行路由跳转时需要用到的代码。最后,我们打开main.dart入口文件,然后注册路由文件。

class App extends StatelessWidget {      
  final _appRouter = AppRouter();      
  @override      
  Widget build(BuildContext context){      
    return MaterialApp.router(      
      routerDelegate: _appRouter.delegate(),      
      routeInformationParser: _appRouter.defaultRouteParser(),      
    );      
  }      
}

2.3 生成路由

当然,auto_route还支持为每个声明的 AutoRoute 生成一个 PageRouteInfo 对象,这些对象包含路径信息以及从页面的默认构造函数中提取的强类型页面参数。

class BookListRoute extends PageRouteInfo {              
  const BookListRoute() : super(name, path: '/books');              
  static const String name = 'BookListRoute';              
}

并且,如果声明的路由有子路由,那么 AutoRoute 会在其构造函数中添加一个子参数,如下。

class UserRoute extends PageRouteInfo {              
   UserRoute({List<PagerouteInfo> children}) :              
    super(              
         name,               
         path: '/user/:id',              
         initialChildren: children);              
  static const String name = 'UserRoute';              
}

2.4 路由跳转

和其他的路由框架一样,AutoRouter 也提供常见的 push、pop 和 remove 方法。比如,我们要打一个新的页面,那么可以使用下面

AutoRouter.of(context).replaceAll([const LoginRoute()]);  //LoginRoute为路由
//或者
AutoRouter.of(context).navigate(const BooksListRoute())       

如果我们使用的是命名路由,那么可以使用navigateNamed()方法,如下。

AutoRouter.of(context).pushNamed('/books') ;

当然,很多时候,路由的跳转还会涉及很多的参数传递,那么对于需要传递参数的路由,我们需要怎么处理呢?对于参数传递,我们可以在目标路由页面使用构造函数的方式,然后再用AutoRouter进行传递。

AutoRouter.of(context).pushAll([IndexRoute(login: true)]);

除了跳转,我们还可能需要处理路由弹栈的场景,对于弹栈,需要用到pop()函数。和其他的路由框架一样,pop()默认只弹出一个,如果要弹出多个,可以使用下面的方式。

//弹出到指定的路由        
context.router.popUntilRouteWithName('HomeRoute');          
//弹出到最顶部
context.router.popUntilRoot();            

 如果要清除,或者删除路由栈里面的内容,可以是呀AutoRouter还提供了remove()函数。

context.router.removeLast();           
context.router.removeWhere((route) => );                  

下面是AutoRouter常用方法的一个汇总。

context.pushRoute(const BooksListRoute());          
context.replaceRoute(const BooksListRoute());          
context.navigateTo(const BooksListRoute());          
context.navigateNamedTo('/books');          
context.navigateBack();         
context.popRoute();

2.5 处理返回结果

有时候,两个路由之间,需要获取页面的处理结果,并将结果返回给上一个页面。对于这种场景,只需要在返回的时候返回结果即可,并在上一个路由使用await进行接收。

router.pop<bool>(true);    
var result = await router.push<bool>(LoginRoute()); 

三、路由导航

3.1 嵌套导航

在应用开发中,嵌套导航是一种比较常见的场景,这意味着,在一个路由页面中嵌套另外的多个路由。

嵌套路由就像父路由的子字段一样。在上面的示例中,UsersPage、PostsPage 和SettingsPage就是DashboardPage的子路由,所以它们的定义如下。

@MaterialAutoRouter(              
  replaceInRouteName: 'Page,Route',              
  routes: <AutoRoute>[              
    AutoRoute(              
      path: '/dashboard',              
      page: DashboardPage,              
      children: [              
        AutoRoute(path: 'users', page: UsersPage),              
        AutoRoute(path: 'posts', page: PostsPage),          
        AutoRoute(path: 'settings', page: SettingsPage),                
      ],              
    ),          
    AutoRoute(path: '/login', page: LoginPage)          
  ],              
)              
class $AppRouter {}

要完成嵌套路由渲染和构建,我们需要在嵌套路由的最外层使用AutoRouter 的小部件,如下。

class DashboardPage extends StatelessWidget {          
  @override          
  Widget build(BuildContext context) {          
    return Row(          
      children: [          
        Column(          
          children: [          
            NavLink(label: 'Users', destination: const UsersRoute()),          
            NavLink(label: 'Posts', destination: const PostsRoute()),          
            NavLink(label: 'Settings', destination: const SettingsRoute()),          
          ],          
        ),          
        Expanded(          
          // nested routes will be rendered here          
          child: AutoRouter(),          
        )          
      ],          
    );          
  }          
}

如果我们需要跳转到嵌套路由的子组件,我们使用下面的方式就可以导航到嵌套路由的子路由。

AutoRoute(              
      path: '/dashboard',              
      page: DashboardPage,              
      children: [              
        AutoRoute(path: '', page: UsersPage),          
        //The same thing can be done using the initial flag          
        //AutoRoute(page: UsersPage,initial: true),              
        AutoRoute(path: 'posts', page: PostsPage),              
      ],              
    ),     

3.2 Tab 导航

前面我们介绍的都是栈管理,即StackRouter,遵循先进后出的逻辑。除了支持StackRouter,auto_route还支持Tab Navigation,下面是示例代码。  

class DashboardPage extends StatelessWidget {          
  @override          
  Widget build(BuildContext context) {          
    return AutoTabsRouter(                 
      routes: const [          
        UsersRoute(),          
        PostsRoute(),          
        SettingsRoute(),          
      ],          
      builder: (context, child, animation) {                    
        final tabsRouter = AutoTabsRouter.of(context);                  
        return Scaffold(          
            body: FadeTransition(          
              opacity: animation,          
              child: child,          
            ),          
            bottomNavigationBar: BottomNavigationBar(          
              currentIndex: tabsRouter.activeIndex,          
              onTap: (index) {             
                tabsRouter.setActiveIndex(index);          
              },          
              items: [          
                BottomNavigationBarItem(label: 'Users',...),          
                BottomNavigationBarItem(label: 'Posts',...),          
                BottomNavigationBarItem(label: 'Settings',...),          
              ],          
            ));          
      },          
    );          
  }          
}

当然,上面的代码看起来有点复杂,所以如果我们只是实现Tab导航,那么可以使用下面的简洁代码。

class DashboardPage extends StatelessWidget {          
 @override            
Widget build(context) {            
 @override          
  Widget build(context) {          
    return AutoTabsScaffold(          
       routes: const [          
        UsersRoute(),          
        PostsRoute(),          
        SettingsRoute(),          
      ],          
      bottomNavigationBuilder: (_,tabsRouter) {          
          return BottomNavigationBar(          
              currentIndex: tabsRouter.activeIndex,          
              onTap: tabsRouter.setActiveIndex          
              items: [          
                BottomNavigationBarItem(label: 'Users',...),          
                BottomNavigationBarItem(label: 'Posts',...),          
                BottomNavigationBarItem(label: 'Settings',...),          
              ],          
            )),                 
       }          
    );          
}

3.3 PageView

当然,我们也可以使用 AutoTabsRouter.pageView 构造函数来实现使用 PageView 的选项卡。

AutoTabsRouter.pageView(      
     routes: [      
        BooksTab(),      
        ProfileTab(),      
        SettingsTab(),      
        ],     
     builder: (context, child, _) {      
        return Scaffold(      
              appBar: AppBar(      
              title: Text(context.topRoute.name),      
              leading: AutoLeadingButton()),      
              body: child,      
              bottomNavigationBar: BottomNavigationBar(          
                    currentIndex: tabsRouter.activeIndex,          
                    onTap: tabsRouter.setActiveIndex          
                    items: [          
                      BottomNavigationBarItem(label: 'Books',...),          
                      BottomNavigationBarItem(label: 'Profile',...),          
                      BottomNavigationBarItem(label: 'Settings',...),          
                    ],          
                  ),    
            ),      
      ); },    
   ); 

3.4 声明式导航

声明式导航需要与 auto_route 一起使用,只需要使用 AutoRouter.declarative 构造函数并返回基于状态的路由列表即可。

AutoRouter.declarative(      
  routes: (handler) => [      
     BookListRoute(),      
     if(_selectedBook != null)      
     BookDetailsRoute(id: _selectedBook.id),      
 ],);

四、高级用法

4.1 路由控制器

事实上,每个嵌套的 AutoRouter 都有自己的路由控制器来管理其内部的堆栈,获得路由控制器最简单的方法是使用上下文。在前面的示例中,我们调用的 AutoRouter.of(context) 就是用来获得根路由控制器的。

需要说明的是,对于渲染嵌套路由的 AutoRouter 小部件,我们使用上面的方式获取的 是小部件树中最近的父控制器而不是根控制器,下面是一个典型的路由控制器的结构示意图。

从上图中可以看出,我们可以通过调用 router.parent() 来访问父路由控制器,对于这个通用函数,在真正调用的时候,我们还需要指定类型,比如StackRouter/TabsRouter。

router.parent<StackRouter>()  
router.parent<TabsRouter>()

当然,如果是获取根路由控制器,那么是不需要进行类型转换的,因为它始终是 StackRouter。

router.root

另一方面,为了在其他地方使用这个路由控制器,可以定义一个全局的key,比如。

class DashboardPage extends StatefulWidget {          
  @override          
  _DashboardPageState createState() => _DashboardPageState();          
}          
class _DashboardPageState extends State<DashboardPage> {          
  final _innerRouterKey = GlobalKey<AutoRouterState>();          
  @override          
  Widget build(BuildContext context) {          
    return Row(          
      children: [          
        Column(          
          children: [          
            NavLink(label: 'Users',          
            onTap:(){          
               final router = _innerRouterKey.currentState?.controller;          
               router?.push(const UsersRoute());          
             }          
            ),          
            ...          
          ],          
        ),          
        Expanded(          
          child: AutoRouter(key: _innerRouterKey),          
        )          
      ],          
    );          
  }          
}

当然,我们也可以在没有全局key的情况下,使用下面的方式获取路由控制器,条件是这个路由已经启动,这个有点类似于Java的反射机制。            

context.innerRouterOf<StackRouter>(UserRoute.name)                        
context.innerRouterOf<TabsRouter>(UserRoute.name)

4.2 Paths

在 AutoRoute 中,使用路径是可选的,因为 PageRouteInfo 对象是按名称匹配的,除非使用根委托中的 initialDeepLink、pushNamed、replaceNamed和navigateNamed 等方法。

如果我们不指定路径,系统将自动生成路径,例如BookListPage 将“book-list-page”作为路径,如果初始 arg 设置为 true,则路径将为“/”。在Flutter开发中,当页面层级比较深时,就可以使用paths方式。

AutoRoute(path: '/books', page: BookListPage),

4.2.1 Path Parameters

当然,我们还可以在paths中添加参数。

AutoRoute(path: '/books/:id', page: BookDetailsPage),

然后,我们只需要在目标路由使用 @PathParam('optional-alias') 方式即可获取传递的参数,比如。

class BookDetailsPage extends StatelessWidget {              
  const BookDetailsPage({@PathParam('id') this.bookId});          
  final int bookId;              
  ...              

4.2.2 Inherited Path Parameters

不过,如果使用 @PathParm() 标识的构造函数参数与路由没有同名的路径参数但它的父级有,那么该路径参数将被继承并且生成的路由不会将此作为参数。

AutoRoute(  
	  path: '/product/:id',  
	  page: ProductScreen,  
	  children: [  
		  AutoRoute(path: 'review',page: ProductReviewScreen),  
	 ],
 ),

当然,我们还可以在路由页面添加一个名为 id 的路径参数,从上面的示例中,我们知道ProductReviewScreen没有路径参数,在这种情况下,auto_route 将检查是否有任何祖先路径可以提供此路径参数,如果有则会标记它作为路径参数,否则会引发错误。

class ProductReviewScreen extends StatelessWidget {  
  const ProductReviewScreen({super.key, @pathParam required String id}); 
}

4.2.3 Query Parameters 

和前面的查询参数的方式相同,只需使用 @QueryParam('optional-alias') 注解构造函数参数即可获取参数的值。

RouteData.of(context).pathParams;                          
context.routeData.queryParams

如果参数名称与路径/查询参数相同,则可以使用 const @pathParam 或者@queryParam 并且不需要传递 slug/别名,比如。

class BookDetailsPage extends StatelessWidget {              
  const BookDetailsPage({@pathParam this.id});          
  final int id;              
  ...

4.2.4 Redirecting Paths

当然,我们也可以使用RedirectRoute来实现路径的重定向,重定向路径时需要使用redirectTo参数指定重定后的路由,比如。

<AutoRoute> [              
     RedirectRoute(path: '/', redirectTo: '/books'),              
     AutoRoute(path: '/books', page: BookListPage),              
 ]    

当然,使用重定向时还可以跟一些参数,比如。

<AutoRoute> [              
     RedirectRoute(path: 'books/:id', redirectTo: '/books/:id/details'),              
     AutoRoute(path: '/books/:id/details', page: BookDetailsPage),              
 ]

除此之外,auto_route 还支持使用通配符来匹配无效或未定义的路径,可以将它作为默认的路径。

AutoRoute(path: '*', page: UnknownRoutePage)                        
AutoRoute(path: '/profile/*', page: ProfilePage)                            
RedirectRoute(path: '*', redirectTo: '/')

4.3 路由守护

我们可以将路由守卫视为中间件或者拦截器,不经过分配的守卫无法将路由添加到堆栈中,这对于限制对某些路由的访问是很有用,相当于在执行路由跳转前我们可以对路由做一些限制。

下面,我们使用 AutoRouteGuard 创建一个路由保护,然后在 onNavigation 方法中实现我们的路由逻辑。

class AuthGuard extends AutoRouteGuard {          
 @override          
 void onNavigation(NavigationResolver resolver, StackRouter router) {          
      //触发条件          
     if(authenitcated){                 
        resolver.next(true);          
      }else{                 
         router.push(LoginRoute(onResult: (success){              
               resolver.next(success);          
          }));          
         }              
     }          
}

在onNavigation方法中,NavigationResolver 对象包含可以调用的属性,所以我们可以使用resolver.route 访问的受保护路由,以及调用resolver.pendingRoutes 访问挂起的路由列表。

接下来,我们将守卫分配给我们想要保护的路线即可,使用方式如下。

AutoRoute(page: ProfileScreen, guards: [AuthGuard]); 

有时候,我们希望获取父窗口包裹的小部件的上下文提供的一些值,那么只需实现 AutoRouteWrapper,并让 WrapRoute(context) 方法返回小部件的子级即可。

class ProductsScreen extends StatelessWidget implements AutoRouteWrapper {          
  @override          
  Widget wrappedRoute(BuildContext context) {          
  return Provider(create: (ctx) => ProductsBloc(), child: this);      
  }          
  ...

4.4 路由观察者

为了方便查看路由栈的具体情况,我们可以通过扩展 AutoRouterObserver 来实现,然后重写里面的函数来进行查看,比如。

class MyObserver extends AutoRouterObserver {          
  @override          
  void didPush(Route route, Route? previousRoute) {          
    print('New route pushed: ${route.settings.name}');          
  }                  
 @override          
  void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {          
    print('Tab route visited: ${route.name}');          
  } 
  @override          
  void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) {          
    print('Tab route re-visited: ${route.name}');          
  }          
}

然后,我们将观察者传递给根委托 AutoRouterDelegate。

return MaterialApp.router(          
      routerDelegate: AutoRouterDelegate(          
        _appRouter,          
        navigatorObservers: () => [MyObserver()],          
      ),          
      routeInformationParser: _appRouter.defaultRouteParser(),        
    );

以上就是Flutter自动路由插件auto_route使用详解的详细内容,更多关于Flutter自动路由插件的资料请关注Devmax其它相关文章!

Flutter自动路由插件auto_route使用详解的更多相关文章

  1. Flutter中文教程-Cookbook

    Flutter中文网的Cookbook中包含了在编写Flutter应用程序时常见问题及示例。设计基础使用主题共享颜色和字体样式Images显示来自网上的图片用占位符淡入图片使用缓存图Lists创建一个基本list创建一个水平list使用长列表创建不同类型子项的List创建一个gridList处理手势处理点击添加Material触摸水波效果实现滑动关闭导航导航到新页面并返回给新页面传值从新页面返回数据给上一个页面网络从网上获取数据进行认证请求使用WebSockets

  2. android-studio – 未配置Dart SDK

    Initializinggradle…

  3. 安卓 – 从一个扑动的应用程序拨打电话

    或者有更好的选择从我的应用程序拨打电话?

  4. android – 如何在Flutter中添加Webview?

    我知道可以将WebView添加为整页,但找不到任何示例代码.我假设你可以使用PageView作为它的基础,但不知道如何调用本机androidWebView并将其添加到PageView.谁能指出我正确的方向?

  5. android – 如何将消息从Flutter传递给Native?

    如果需要与特定的API/硬件组件进行交互,您如何将Flutter的信息传递回Android/Native代码?是否有任何事件频道可以通过其他方式发送信息或类似于回调?

  6. android – 如何在Flutter App中处理onPause / onResume?

    我是否过于复杂的事情?即使我的用例似乎不需要它,我仍然想知道:如何自己处理onPause/onResume事件?

  7. android – 如何使用Flutter构建Augment Reality应用程序?

    我对Android开发有一些基础知识.最近听说过Flutter并且非常有兴趣研究它.我想知道是否有可能使用颤振构建增强现实应用程序以及要实现此目的的方法?请帮忙.解决方法截至目前,颤振不支持3D.Flutter现在专注于2D,团队长期计划为颤振提供优化的3Dapi.你读了常见问题here.

  8. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. Flutter StreamBuilder实现局部刷新实例详解

    这篇文章主要为大家介绍了Flutter StreamBuilder实现局部刷新实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. jQuery插件HighCharts绘制的基本折线图效果示例【附demo源码下载】

    这篇文章主要介绍了jQuery插件HighCharts绘制的基本折线图效果,结合实例形式分析了jQuery基于HighCharts插件绘制图形的具体实现步骤与相关操作技巧,并附带demo源码供读者下载参考,需要的朋友可以参考下

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部