【Flutter】路由功能

5,592 阅读6分钟

跳转功能

举例常用页面跳转功能。

普通跳转

  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (_) => NextPage1(),
    ),
  );

命名跳转

Navigator.of(context).pushNamed("/router/nextPage");

前提是需要在程序主入口配置路由表

MaterialApp(
      navigatorObservers: [UserNavigatorObserver()],
      initialRoute: "/",
      routes: {
        "/router": (context) => RouterDemo(),
        "/router/nextPage": (context) => NextPage2(),
      },
    );

退出页面

Navigator.of(context).pop(); //直接做退出操作
Navigator.of(context).maybePop(); //若为栈中最后一个页面不做退出操作

高级用法

popAndPushNamed

退出当前页面并跳转新页面

Navigator.of(context).popAndPushNamed("/router/nextPage");

等同于如下操作

Navigator.of(context).pop();
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => NextPage1(),
  ),
);

PS: 但需要注意如果当前页为栈底根页面先执行pop再执行push会不会和popAndPushNamed执行结果不同呢?看源码就能会发现popAndPushNamed其实就是先执行pop再执行push的操作所以两者执行结果一样。

  @optionalTypeArgs
  Future<T> popAndPushNamed<T extends Object, TO extends Object>(
    String routeName, {
    TO result,
    Object arguments,
  }) {
    pop<TO>(result);
    return pushNamed<T>(routeName, arguments: arguments);
  }

pushReplacementNamed

用法和popAndPushNamed类似,同样是退出当前页面并跳转新页面。但popAndPushNamed页面出栈和入栈都有动画,pushReplacementNamed则只有入栈动画

Navigator.of(context).pushReplacementNamed("/router/next5");

pushNamedAndRemoveUntil

将路由栈出栈到对应命名路由然后跳转到新页面,如下所示代码“ModalRoute.withName("/")”是将路由栈退到根路由并跳转到“/router/nextPage”命名路由。 举例当前路由栈为[1,2,3,4],执行pushNamedAndRemoveUntil打开新页面5,设置 ModalRoute.withName为2,最后路由栈为[1,2,5]。常用场景例如在个人设置页面中多级子菜单跳转最后直接回到主页操作。

Navigator.of(context).pushNamedAndRemoveUntil(
                "/router/nextPage",
                ModalRoute.withName("/"),
              );

PS: 这里要注意的是如果入栈非命名路由,不采用pushNamed入栈则ModalRoute.withName("/")会找不到要回退的位置,则跳转的页面不带返回键,也就是说栈中就只剩下一个页面。这点真的好奇是不是Flutter路由设计的问题,我暂时也没有找到ModalRoute.withName替代方法所以回退的页面路由必须是命名路由。

removeRoute

路由栈移除功能,例如可以通过ModalRoute.of(context)获取当前页面路由。如果和Navigator.of(context).pop()做比较的话就是通过该方法可以移除指定路由,只要拿到对应路由对象就能将其在栈中移除。而Navigator.of(context).pop()则只能移除当前路由页面。

Navigator.of(context).removeRoute(ModalRoute.of(context));

removeRouteBelow

同样是路由栈移除功能,根据输入页面路由移除该锚点路由的上一个页面路由。 例如路由栈为[1,2,3,4],执行removeRouteBelow(3)后路由栈变为[1,3,4]。

Navigator.of(context).removeRouteBelow(ModalRoute.of(context));

replace

替换路由栈功能,类似于pushReplacementNamed。

PS:The old route must not be currently visible 在官方文档中对replace方法的介绍,不能用于栈顶路由。所以该方法并不能用做当前路由使用,可以移除上一个或栈中路由。

因此这里终于可用使用到UserNavigatorObserver中全局路由栈数组history获取指定路由将其从栈中移除。

Navigator.of(context).replace(
    oldRoute: UserNavigatorObserver
        .history[UserNavigatorObserver.history.length - 2],
    newRoute: MaterialPageRoute(
      builder: (_) => NextPage1(),
    ),
 );

例如路由栈为[1,2,3,4],replace(3,5)后路由栈变为[1,2,5,4]。

replaceRouteBelow

替换路由栈功能,于removeRouteBelow同理。替换输入路由替换掉该锚点路由的上一个页面路由。最后和replace一样不能作用于当前页面路由。

Navigator.of(context).replaceRouteBelow(
  anchorRoute: UserNavigatorObserver
      .history[UserNavigatorObserver.history.length - 2],
  newRoute: MaterialPageRoute(
    builder: (_) => NextPage1(),
  ),
);

例如路由栈为[1,2,3,4],replace(3,5)后路由栈变为[1,5,3,4]。

传参功能

路由同时支持页面之间的参数传递

入参传递

路由跳转通过arguments字段传递想要的入参数据,我们可以设定arguments为map对象传递更多入参。命名路由和直接路由传递arguments略有不同,命名路由直接通过arguments字段传递,直接路由是通过settings字段创建RouteSettings对象中的arguments传递。

  • 以命名路由跳转时
Navigator.of(context).pushNamed(
  "/router/data2",
  arguments: {"data": "Hello"},
);
  • 以页面跳转时
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => ChildDataDemo4(),
    settings: RouteSettings(
      arguments: {"data": "Hello"},
    ),
  ),
);

接收页面通过ModalRoute.of(context).settings获取入参arguments得到数据。

class RouterChildDateDemo2 extends StatefulWidget {
  @override
  _RouterChildDateDemo2State createState() => _RouterChildDateDemo2State();
}

class _RouterChildDateDemo2State extends State<RouterChildDateDemo2> {
  @override
  Widget build(BuildContext context) {
    Map arguments = ModalRoute.of(context).settings.arguments;
    String data = arguments['data'];
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          Text("data: $data"),
        ],
      ),
    );
  }
}
  • 以直接路由构造方法
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => ChildDateDemo3("Hello"),
  ),
);

数据接收页面通过widget获取入参。当然这种方式耦合性太高,对后期维护不利不推荐使用(虽然我自己项目中都是这么写,之后重构工作量巨大😿)。

class ChildDateDemo3 extends StatefulWidget {
  final String data;

  ChildDateDemo3(this.data);

  @override
  _ChildDateDemo3State createState() => _ChildDateDemo3State();
}

class _ChildDateDemo3State extends State<ChildDateDemo3> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          Text("data: ${widget.data}"),
        ],
      ),
    );
  }
}

由于context上下文的缘故若传参需要在initState中使用时,使用SchedulerBinding.instance.addPostFrameCallback获取参数。

SchedulerBinding.instance.addPostFrameCallback((_) {
  Map arguments = ModalRoute.of(context).settings.arguments;
  cusip = arguments["cusip"];
});

参数回传

除了入参外,我们当然希望能够在传递参数同时可以获取到返回值。比如进入一个设置页面在返回时获取到上个页面设置数据并显示(虽然全局状态管理就能很好代替这方式)。

路由跳转方法是有返回值Future,将跳转方法设置为async通过await获取结果。

var result = await Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => ChildDataDemo5(),
  ),
);
setState(() {
  this.result = result.toString();
});

当要返回上级页面回传结果时在pop方法中传递需要返回泛型参数(使用字典是比较好的数据结构)。

Navigator.of(context).pop({"data": "Bye"});

动画过渡

替换MaterialPageRoute使用PageRouteBuilder做路由跳转。通过transitionsBuilder创建AnimatedWidget对象实现动画效果。如下设置一个SlideTransition滑动过渡动画通过起始和结束的偏移量达到侧边滑出效果

Navigator.of(context).push(
  PageRouteBuilder(
    pageBuilder: (BuildContext context,
        Animation<double> animation1,
        Animation<double> animation2) {
      return AniDemo2();
    },
    transitionsBuilder: (BuildContext context,
        Animation<double> animation1,
        Animation<double> animation2,
        Widget child) {
      return SlideTransition(
        position: Tween<Offset>(
          begin: Offset(-1.0, 0.0),
          end: Offset(0.0, 0.0),
        ).animate(
          animation1,
        ),
        child: child,
      );
    },
  ),
);

路由全局监听

路由同时支持监听功能,通过NavigatorObserver可以实时获取路由进栈出栈信息。在根组件中创建路由观察者NavigatorObserver,如下创建UserNavigatorObserver继承NavigatorObserver通过回调接口记录全局栈信息。下面就介绍NavigatorObserver回调接口的触发条件。

MaterialApp(
    navigatorObservers: [UserNavigatorObserver()],
    ......
)

继承实现NavigatorObserver并增加全局history栈信息管理数组。

class UserNavigatorObserver extends NavigatorObserver {
  static List<Route<dynamic>> history = <Route<dynamic>>[];

  @override
  void didPop(Route route, Route previousRoute) {
    super.didPop(route, previousRoute);
    history.remove(route);
    ///调用Navigator.of(context).pop() 出栈时回调
  }

  @override
  void didPush(Route route, Route previousRoute) {
    super.didPush(route, previousRoute);
    history.add(route);
    ///调用Navigator.of(context).push(Route()) 进栈时回调
  }

  @override
  void didRemove(Route route, Route previousRoute) {
    super.didRemove(route, previousRoute);
    history.remove(route);
    ///调用Navigator.of(context).removeRoute(Route()) 移除某个路由回调
  }

  @override
  void didReplace({Route newRoute, Route oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    history.remove(oldRoute);
    history.add(newRoute);
    ///调用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new")) 替换路由时回调
  }

  @override
  void didStartUserGesture(Route route, Route previousRoute) {
    super.didStartUserGesture(route, previousRoute);
    ///iOS侧边手势滑动触发回调 手势开始时回调
  }

  @override
  void didStopUserGesture() {
    super.didStopUserGesture();
    ///iOS侧边手势滑动触发停止时回调 不管页面是否退出了都会调用
  }
}

didPop

调用Navigator.of(context).pop()或其他出栈方法时回调

didPush

调用Navigator.of(context).push(Route())或其他进栈方法时回调

didRemove

调用Navigator.of(context).removeRoute(Route()) 移除某个路由回调

didReplace

调用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new")) 替换路由时回调

didStartUserGesture

iOS侧边手势滑动触发回调 手势开始时回调

didStopUserGesture

iOS侧边手势滑动触发停止时回调 不管页面是否退出了都会调用

🚀完整代码看这里🚀

参考