Fluro的使用和封装

697 阅读3分钟

Flutter路由框架-Fluro的使用和封装

依赖

fluro: ^2.0.3

Fluro使用步骤

构建FluroRouter路由实例,单例

FluroRouter本身已经是单例模式

/// The static / singleton instance of [FluroRouter]
///
/// {@macro fluro_router}
static final appRouter = FluroRouter();

定义路由路径的处理器(Handler

用于匹配不同路由路径的处理方法

static Map<String, Handler> pageRoutes = {
  //一般‘/’作为首页路由
  '/': Handler(
      handlerFunc: (context, Map<String, List<String>> parameters) =>
          const HomePage()),
  '/category/:id':
      Handler(handlerFunc: (context, Map<String, List<String>> parameters) {
      String id = parameters['id']?.first ?? '0';
    return const CategoryPage(id);
  }),
  '/detail/:id':
      Handler(handlerFunc: (context, Map<String, List<String>> parameters) {
    return const DetailPage();
  }),
  '/login':
      Handler(handlerFunc: (context, Map<String, List<String>> parameters) {
    return const LoginPage();
  })
}

格式说明:

HandlerFunc接收上下文 context,以及携带了路由参数,这个参数是一个Map,对应路由路径的多个路由参数。例如/dynamic/:id 路由,如果实际路由为/dynamic/1?event=a&event=b,则 parameters 的格式如下:

{
  "id": ["1"],
  "event": ["a", "b"]
}

这样其实也只是传递一些简单数据,如果传递复杂数据需要依靠RouteSettings,后面会封装

路由设置

MaterialApp 中把 onGenerateRoute设置为 FluroRouter.generator方法来构建系统路由

class AppRoutes {
  static final route = FluroRouter();
​
  static setUp() {
    //设置路由未找到的页面
    route.notFoundHandler =
        Handler(handlerFunc: (context, parameters) => const NotFoundPage());
        
    AppPageHandler.pageRoutes.forEach((path, handler) {
      route.define(path,
          handler: handler, transitionType: TransitionType.inFromRight);
    });
  }
}
void main() {
  AppRoutes.setUp();
  runApp(const MyApp());
}
MaterialApp(onGenerateRoute: AppRoutes.route.generator)

路由跳转

  • 普通跳转

    /// Similar to [Navigator.push] but with a few extra features.
    Future navigateTo(BuildContext context, String path,
        {bool replace = false,//是否替换当前页面
        bool clearStack = false,//是否清除当前栈中的页面
        bool maintainState = true,//是否保持状态
        bool rootNavigator = false,//是否是跟路由
        TransitionType? transition,//动画方式
        Duration? transitionDuration,//动画执行时间
        RouteTransitionsBuilder? transitionBuilder,
        RouteSettings? routeSettings}) {//可传递数据
    AppRoutes.route.navigateTo(context, 'category/$index')
    
  • 携带大量参数跳转

    AppRoutes.route.navigateTo(
                        context, 'category/$index',
                        routeSettings: const RouteSettings(arguments: category)),
    

    CategoryPage接收category参数

    @override
      Widget build(BuildContext context) {
        var arguments = ModalRoute.of(context)?.settings.arguments;
        Category? category;
        if (arguments != null && arguments is Category) {
          category = arguments;
        }
        ...
      }
    

    注意不要在initState中获取参数,因为此时页面树还未创建完成

  • 清除路由堆栈跳转

    AppRoutes.router.navigateTo(context, nextPath, clearStack: true);
    
  • 仅清除当前页面栈

    AppRoutes.router.navigateTo(context, homePath, replace: true);
    
  • back 返回

    void pop<T>(BuildContext context, [T? result]) =>
        Navigator.of(context).pop(result);
    
    AppRouter.appRouter.pop(context)
    

Fluro优缺点

  • 优点

    1. 使用简单
    2. 支持大量的路由动画
    3. 支持路由拦截
    4. 支持Web 路由格式
  • 缺点

    1. 不支持Navigator2
    2. pop方式单一,复杂场景需要依靠Navigator
    3. 传递大量参数麻烦,需要依靠RouteSettings
    4. 路由拦截需要自己实现

封装

封装

  1. 支持复杂参数
  2. pop支持复杂场景(使用Navigator实现)
  3. 自定义重定向(路由拦截)
  4. github源码
  • 复杂参数的封装 Bundle

    class Bundle {
      final Map<String, dynamic> _map = {};
    
      putInt(String k, int v) => _map[k] = v;
    
      int getInt(String k) => _map[k] ?? 0;
    
      putString(String k, String v) => _map[k] = v;
    
      String getString(String k) => _map[k] ?? '';
    
      putBool(String k, bool v) => _map[k] = v;
    
      bool getBool(String k) => _map[k] ?? false;
    
      putList<T>(String k, List<T> v) => _map[k] = v;
    
      List<T> getList<T>(String k) => _map[k] ?? [];
    
      putMap(String k, Map v) => _map[k] = v;
    
      Map getMap(String k) => _map[k] ?? {};
    
      put<T>(String k, T t) => _map[k] = t;
    
      T? get<T>(String k) => _map[k];
    
      @override
      String toString() {
        return _map.toString();
      }
    }
    
  • 继承FluroRouter进行扩展

    class _AppRouter extends FluroRouter {
      ///bundle and routeSettings only one
      ///Both can use bundle.=>>routeSettings.arguments=bundle
      @override
      Future navigateTo(BuildContext context, String path,
          {bool replace = false,
          bool clearStack = false,
          bool maintainState = true,
          bool rootNavigator = false,
          RedirectInterceptor? interceptor,
          String? name,
          Bundle? bundle,
          TransitionType? transition,
          Duration? transitionDuration,
          RouteTransitionsBuilder? transitionBuilder,
          RouteSettings? routeSettings}) {
        if (interceptor?.needRedirect() ?? false) {
          return interceptor!.navigateTo(context);
        }
        if (bundle != null) {
          routeSettings = RouteSettings(arguments: bundle, name: name ?? path);
        }
        return super.navigateTo(context, path,
            replace: replace,
            clearStack: clearStack,
            maintainState: maintainState,
            rootNavigator: rootNavigator,
            transition: transition,
            transitionDuration: transitionDuration,
            transitionBuilder: transitionBuilder,
            routeSettings: routeSettings);
      }
    
      ///back home and clear other page
      ///example:Home ->A->B->C:popToHome ->Home
      void popToHome(BuildContext context) {
        popUntil(context, (route) => route.isFirst);
      }
    
      @override
      void pop<T>(BuildContext context, [T? result]) {
        if (canPop(context)) {
          super.pop(context, result);
        }
      }
    
      void popUntil(BuildContext context, RoutePredicate predicate) {
        Navigator.popUntil(context, predicate);
      }
    
      bool canPop(BuildContext context) {
        return Navigator.canPop(context);
      }
    }
    
    final AppRouter = _AppRouter();
    
  • 自定义重定向(路由拦截)

    abstract class RedirectInterceptor {
      //是否需要重定向
      bool needRedirect();
     
      //重定向页面导航
      Future navigateTo(BuildContext context);
    }
    

封装后再使用

  • 自定义登录重定向

    class LoginInterceptor extends RedirectInterceptor {
      @override
      Future navigateTo(BuildContext context) {
        return AppRouter.navigateTo(context, AppPagePath.login);
      }
    
      @override
      bool needRedirect() {
        return !AppManager.getInstance().isLogin;
      }
    }
    
  • 跳转时需要检查是否登陆

     onPressed: () => AppRouter.navigateTo(context,AppPagePath.settingMessage,
         interceptor: LoginInterceptor()),
    
  • 跳转时传递复杂数据

     onTap: () => AppRouter.appRouter.navigateTo(context, AppPagePath.detail,
         bundle: Bundle()..put('detail', model)),
    

复杂场景下的pop

  • 返回至上一级

    AppRouter.pop(context))
    
  • 返回至首页(清空首页上面的页面栈)

    AppRouter.popToHome(context))
    
  • 手动控制保留那些page ,只保留首页以及category页面

    AppRouter.popUntil(context, (route) => route.isFirst || route.settings.name == 'category');
    
  • 返回至首页(底部Tab的第一个page) 需要使用状态管理比如:Provider

    onPressed: () {
      context.read<AppTabModel>().curIndex = 0;
      AppRouter.popToHome(context);
    }),
    

    \