Flutter路由导航与页面生命周期

页面在flutter中是用路由(Route)的概念来表示的,Route是对屏幕界面的抽象,每一个页面在页面栈中对应一个Route对象。 Flutter使用Navigator来管理页面栈,通过页面栈的入栈和出栈来显示页面跳转和返回,Navigator提供了很多方法来管理Route的出栈和入栈,如Navigator.push Navigator.pushNamed Navigator.pop等方法。基本上Navigator提供两种方式来实现页面跳转:

  • 直接push Route对象
  • 通过name来push相应页面
1. 跳转页面基本用法
Navigator.push(
    context,
    new MaterialPageRoute(builder: (context) => new SecondScreen()),
  );

可以直接使用MaterialPageRoute来实现Route对象,MaterialPageRoute需要一个builder参数来生成页面widget,它通过自适应的平台的过渡效果来切换页面。默认情况下,当一个路由push进来,上一个路由将保留在内存中,如果想释放所有资源,可以将MaterialPageRoute的 maintainState属性设置为 false。

另一种是name来push页面,可以预先定义页面name,如下:

new MaterialApp(
      title: 'Navigation',
      initialRoute: '/',
      routes: <String, WidgetBuilder>{
        '/': (BuildContext context) => new HomeScreen(),
        '/second': (BuildContext context) => new SecondScreen(),
      },
    );

之后可以使用下面方法切换页面:

Navigator.of(context).pushNamed('/second');
2. 页面间传值

现实情况下,往往需要上一级页面需要携带参数值到下一级页面。

  1. 如果使用第一种方式直接push Route对象的方法传值比较简单,需要在下一级页面widget构造方法定义接收的参数,然后build widget的时候使用带参数的构造方法即可,如:
class SecondScreen extends StatelessWidget {
  final String param1;
  final int param2;

  const SecondScreen({Key key, this.param1, this.param2}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return  ...;
  }
}
Navigator.push(
    context,
    new MaterialPageRoute(builder: (context) => new SecondScreen(param1:'xxxxx', param2: 1234)),
  );
  1. 如果使用name来跳转,需要在pushNamed方法中添加arguments参数,这里的routeName和arguments将会被封装成RouteSettings对象传递到Route构建方法中,如:
Navigator.pushNamed(context, routeName, arguments: args);

然而如果在MaterialApp中预先使用routes参数定义了routeName和WidgetBuilder的对应关系后,系统会自动生成route而不会回调给我们,从而无法给生成的widget携带参数。
我采用的方法是不预先设置routes,而是通过onGenerateRoute(RouteSettings settings)回调来生成route对象,RouteSettings对象携带了前面传递的routeName和arguments。
首先,定义一个结构和一个Map存储页面信息和pageBuilder, 这里的shouldLogin是处理业务中是否需要先登录情况,可以根据实际情况改造MyPageBuilder:

class MyPageBuilder {
  final Function pageBuilder;
  final bool shouldLogin;

  MyPageBuilder(this.pageBuilder, this.shouldLogin);
}

//定义routeName与MyPageBuilder关系,并处理参数
static Map<String, MyPageBuilder> routes = {
    PAGE_SPLASH: MyPageBuilder(() => new SplashPage(), false),
    PAGE_SECOND: MyPageBuilder((args) {
      int index = 0;
      if (args != null) {
        try {
          index = int.parse(args['index']);
        } catch (e) {
          index = 0;
        }
      }
      return new SecondScreen(
        param2: index,
      );
    }, false),
  };

然后在onGenerateRoute(RouteSettings settings)生成Route对象,在生成Route时,需要设置settings参数,用于标识该Route的信息,后面会介绍作用。

 PageRoute onGenerateRoute(RouteSettings settings) {
    Logger.d(settings.toString());
    String routeName = settings.name;

    MyPageBuilder myPageBuilder = routes[routeName];
    if (pageBuilderWithLogin == null) {
      Logger.e('router name $routeName can not recognized!!!');
      return null;
    }
    Function pageBuilder = myPageBuilder.pageBuilder;
    if (pageBuilder is Widget Function(Map<String, String> args)) {
      dynamic args = settings.arguments;
      return MaterialPageRoute(
          builder: (context) => pageBuilder(args), settings: settings);
    } else {
      return MaterialPageRoute(
          builder: (context) => pageBuilder(), settings: settings);
    }

这样可以将参数传递给目标widget了。
实际上,系统在通过name导航的时候,首先查找在routes中是否有对应的配置,如果有,直接调用pageBuilder方法生成widget,进而生成Route对象;如果没查到,将routeName和arguments包装乘RouteSettings,传递调用onGenerateRoute(RouteSettings settings),返回Route。我们就是在onGenerateRoute方法中处理传参的。

3. 页面返回及回传参数
  1. 如果无需返回值,直接调用pop方法即可
Navigator.pop(context);
  1. 如果需要返回值,需要增加一个参数
Navigator.pop(context, 'back param');

接收页面需要在跳转时使用await接收该返回参数,如:

jumpAndCallback: () async {
  String result = await Navigator.push(
    context,
    new MaterialPageRoute(
      builder: (context) => new SecondScreen(),
    ),
  );

 print('back params  is $result');
}
4. Navigator其他管理栈方法
  1. pushReplacement / pushReplacementNamed
    替换栈顶Route,相当于先pop栈顶,再push新的页面
  2. popUntil
    将制定页面元素上方的元素全部pop,该方法接收一个参数RoutePredicate,它是一个方法,返回bool,判定是否已经到达了制定元素,这里使用name方式标记Route就非常方便使用ModalRoute.withName(untilName)来实现这个RoutePredicate方法,也是上文中需要将RouteSettings添加到Route中的原因。
Navigator.popUntil(context, ModalRoute.withName('/initScreen'));
  1. pushAndRemoveUntil / pushNamedAndRemoveUntil
    顾名思义,该方法先pop到制定页面元素,然后再push一个新的页面。可以使用该方法pop所有页面再打开首页,可以达到回到首页并刷新首页的目的。
  2. canPop
    判断是否可以安全地pop掉当前页面,canPop只有在栈中只有一个元素的时候返回 false, 其它都是 true.
  3. maybePop
    如果你pop掉最后一个页面元素,那整个app就显示一片黑色。所以不应该pop掉栈底的元素,用maybePop可以帮助你,当只有一个元素的时候不会pop,其他情况会pop掉。

还有其他方法,可以查看源码和注释,基本都能看明白,这里就不赘述了。

5. 页面生命周期

原生开发中,经常需要在页面页面重新可见和不可见状态的生命周期回调做一些事情,比如页面数据刷新和状态保存和恢复等。如android Activity的onResume和onPause。
flutter在StatefulWidget中有创建state和销毁state的回调周期函数,initState和dispose方法,但 flutter没有直接在widget中给出类似onResume onPause的生命周期,好在flutter在Route切换的时候有一些监听可以获取到这两个生命周期。
前面有同学已经将State的生命周期回调封装到了一个pub库中,flutter_lifecycle_state,有兴趣的可以去看看。

  1. WidgetsBindingObserver
    首先可以使用WidgetsBindingObserver来监听Application lifecycle,如应用后台和应该重新从后台恢复。
 class AppLifecycleReactor extends StatefulWidget {
   const AppLifecycleReactor({ Key key }) : super(key: key);

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

 class _AppLifecycleReactorState extends State<AppLifecycleReactor> with WidgetsBindingObserver {
   @override
   void initState() {
     super.initState();
     WidgetsBinding.instance.addObserver(this);
   }

   @override
   void dispose() {
     WidgetsBinding.instance.removeObserver(this);
     super.dispose();
   }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        onResume();
        break;
      case AppLifecycleState.paused:
        onPause();
        break;
      default:
        break;
    }
  }

  void onResume() {}

  void onPause() {}

   @override
   Widget build(BuildContext context) {
     return Text('Last notification: $_notification');
   }
 }
  1. MaterialApp中navigatorObservers监听
    navigatorObservers需要一个NavigatorObserver列表,我们可以继承NavigatorObserver来监听回调。
MaterialApp(
      navigatorObservers: [routeObserver],
      onGenerateRoute: AppNavigator.onGenerateRoute,
      initialRoute: AppNavigator.PAGE_SPLASH,
    )
class MyObserver extends NavigatorObserver {
  factory MyObserver() => _getInstance();
  static MyObserver get instance => _getInstance();
  static MyObserver _instance;

  BuildContext curContext;

  MyObserver._internal() {}
  static MyObserver _getInstance() {
    if (_instance == null) {
      _instance = new MyObserver._internal();
    }
    return _instance;
  }

  String topRouteName = "";

  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    notifyHide(previousRoute);
    notifyShow(route);
  }

  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
    notifyHide(route);
    notifyShow(previousRoute);
  }

  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
    notifyHide(route);
    notifyShow(previousRoute);
  }

  /// The [Navigator] replaced `oldRoute` with `newRoute`.
  void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
    notifyHide(oldRoute);
    notifyShow(newRoute);
  }

  void notifyShow(Route<dynamic> route) {}

  void notifyHide(Route<dynamic> route) {}

}

其中didPush,didPop,didRemove,didReplace是Navigator相应动作的回调,通过这些方法可以知道哪个Route正在出栈,哪个Route正在入栈。Route中可以访问RouteSettings对象获取Route name。然后通知相应的Route中widget就行了,可以采用广播等方式通知到相应的State。

6. 参考资料

Flutter中文网
Flutter 官网
Flutter 路由和导航
flutter_lifecycle_state

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 猗嗟昌兮,颀而长兮。 抑若扬兮,美目扬兮。 巧趋跄兮,射则臧兮。 猗嗟名兮,美目清兮。 仪既成兮。终日射侯, 不出...
    静棠阅读 378评论 0 0
  • 天边等着日暮, 天的边际, 酝酿着日暮的温柔, 如你嫣然一笑。 湖边等着明月, 湖的中心, 凝结着月色的皎洁, 如...
    我的发呆日记阅读 184评论 0 0
  • 恭喜 真拭悟慧 获得得到15专栏分享群的首期“周末原创分享”活动的第一名! 获奖文章: 要不要树立目标?我终于找到...
    GEEKYANG阅读 732评论 0 0
  • 一丝丝的不确定来源于你的举棋不定,源于你的若近若离,源于你的徘徊游离。将对方纳入未来的考量是爱一个人的体现,觉得不...
    沫戚阅读 88评论 0 0
  • 昨天是今年的七夕情人节,我度过了第一个有男朋友的情人节。 并没有什么惊喜和不一样的感悟,出去吃顿饭,然后回家,第二...
    善良的老约翰阅读 324评论 0 0