Flutter 入门 - 路由导航

509 阅读3分钟

Flutter入门专栏持续更新

概述

路由由来已久,例如 iOS 中使用的 URL 解析,遵守URI标准,通过 path 映射 controller,核心就是一个字典(Map),我们也称之为命名路由。在 Flutter 中,使用 Route 进行路由(页面)管理,Navigator进行路由跳转操作。

Navigator

对 Route 进行管理,就是一个栈,和iOS中的 navigationController 类似。

基本跳转

1、获取 Navigator

  • Navigator.of(context)

2、push 方法

  • Navigator.of.push

    Navigator.of(context).push(
      // 页面要向被路由统一管理,必须包装为 Route, 但是 Route 是一个抽象类,所以是不能实例化的,一般使用 MaterialPageRoute
      MaterialPageRoute(
        builder: (cxt) {
          return DetailPage("a message from home");
        },
      ),
    );
    
  • Navigator.of().pushNamed

    Navigator.of(context).pushNamed("/setting");
    
  • Navigator.pushNamed()

    // 等同于上面
    Navigator.pushNamed(context, AboutPage.pageName);
    

以上三种push 方法,返回值都是 Future ,因此可以等待pop带回来的信息

3、pop方法

  • Navigator.of(context).pop
future.then((value) {
  // pop 携带回来的信息
  print(value);
  setState(() {
    _message = value;
  });
});

4、拦截返回

除了按钮点击可以直接掉 pop 方法之外,我们还可以拦截左上角返回按钮的行为

Scaffold 外嵌套 WillPopScope 实现 onWillPop 则可以拦截返回

 @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () {
        //拦截返回按钮
        Navigator.of(context).pop("a detail message");
      },
      child: Scaffold()
    );
  }

参数传递

1、普通传递

Navigator.of(context).push 通过构造器的方式传递,一般应用使用路由的话,使用构造器传递参数会比较少

final future = Navigator.of(context).push(
  MaterialPageRoute(
    builder: (cxt) {
      // 传递的是 DetailPage 构造器所需的参数
      return DetailPage("a message from home");
    },
  ),
);

2、arguments

pushNamed 通过 arguments 传递参数

// arguments 是 Object 类型,因此可以传递任意参数
Navigator.pushNamed(context, AboutPage.pageName,
    arguments: "a message from home");

Route

我们一般使用命名路由的映射关系来统一管理页面

命名路由

一般将路由放在 MaterialApp 中

return MaterialApp(
  title: '路由导航',
  theme: ThemeData(
      primarySwatch: Colors.blue, splashColor: Colors.transparent
  ),
  initialRoute: "/",
  routes: {
    "/home": (ctx) => HomePage(),
    "/detail": (ctx) => DetailPage()
  },
);

在开发中,为了让每个页面对应的routeName统一,我们通常会在每个页面定义一个路由常量来使用

class AboutPage extends StatefulWidget {
  static const String pageName = "/about";
}

然后可以将路由改为:

routes: {
  HomePage.pageName: (cxt) => HomePage(),
  AboutPage.pageName: (cxt) => AboutPage()
}

参数获取

之前讲了通过 构造器方式 以及 arguments 传递参数,现在看通过 arguments 传递的参数怎么获取

通过 ModalRoute.of(context).settings.arguments 获取传参

class _AboutPageState extends State<AboutPage> {
  @override
  Widget build(BuildContext context) {
    final _message = ModalRoute.of(context).settings.arguments as String;
    ...
    ...
  }
}

路由勾子

onGenerateRoute

比如有的页面(DetailPage)有构造方法,但是我们也想通过命名路由的方式进行管理,像之前的

"/detail": (ctx) => DetailPage() 就无法映射,这时候需要使用 onGenerateRoute,其实也就是自己手动去生成路由 MaterialPageRoute 进行管理。

onGenerateRoute: (settings) {
  if (settings.name == "/detail") {
    return MaterialPageRoute(
      builder: (ctx) {
        return DetailPage(settings.arguments);
      }
    );
  }
  return null;
},

push 的时候则使用 arguments 传递参数

Navigator.pushNamed(context, DetailPage.pageName,
    arguments: "a message from home");

onUnknownRoute

错误页面:如果代码里的路由未添加到 routes 里面,则需要做安全防护。

onUnknownRoute: (settings) {
  return MaterialPageRoute(
    builder: (cxt) {
      //定义一个错误页面 UnknownPage
      return UnknownPage();
    },
  );
 }

路由管理类

路由通常放在一个单独的类中去管理,这样防止 main.dart 中代码堆积

定义 Routes 类,创建静态方法

  • routes
  • initialRoute
  • onGenerateRoute
  • onUnknownRoute

在main.dart 里就简化成

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      // 所有路由
      routes: Routes.routes,
      //启动页 初始页  "/" == HomePage
      initialRoute: Routes.initialRoute,
      // 勾子路由, 因为页面必须构造器初始化,所以需要在此拦截自己手动生成 Route
      onGenerateRoute: Routes.onGenerateRoute,
      // 路由找不到的错误页面
      onUnknownRoute: Routes.onUnknownRoute,
    );
  }
}

附 Routes 类中代码:

class Routes {
  static final Map<String, WidgetBuilder> routes = {
    // "/about" : (cxt) => AboutPage(),
    AboutPage.pageName: (cxt) => AboutPage(),
    // DetailPage.pageName:(cxt) => DetailPage()
    HomePage.pageName: (cxt) => HomePage()
  };
​
  static final String initialRoute = HomePage.pageName;
​
  static RouteFactory onGenerateRoute = (settings) {
    if (settings.name == DetailPage.pageName) {
      return MaterialPageRoute(
        builder: (cxt) {
          return DetailPage(settings.arguments);
        },
      );
    }
    return null;
  };
​
  static RouteFactory onUnknownRoute = (settings) {
    return MaterialPageRoute(
      builder: (cxt) {
        return UnknownPage();
      },
    );
  };
}