Flutter路由管理(二)

发布于:2024-10-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

路由(Route)在移动开发中通常是指页面(Page),这与Web开发的意义是相同的,Route在Andriod中通常指一个Activaty,在IOS中指一个ViewController,路由入栈(push)用于打开一个新页面,路由出栈(pop)操作用于对应页面关闭操作,路由管理则是指如何管理路由栈。

1、示例

  • 创建一个新路由,新页面在页面中间显示一句话
class NewRoute extends StatelessWidget {
  const NewRoute({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("新页面"),
      ),
      body: const Center(
        child: Text("新路由"),
      ),
    );
  }
}
  • 在main.dart的_MyHomePageState类中添加以下按钮
ElevatedButton(
                style: ButtonStyle(
                    foregroundColor:
                        WidgetStateProperty.all(Colors.amberAccent)),
                onPressed: () {
                  Navigator.push(context, MaterialPageRoute(builder: (context) {
                    return const RouteTestRoute();
                  }));
                },
                child: const Text("路由管理")),
1.1 MaterialPageRoute
MaterialPageRoute({
    required this.builder,
    super.settings,
    this.maintainState = true,
    super.fullscreenDialog,
    super.allowSnapshotting = true,
    super.barrierDismissible = false,
})
  • required this.builder:是一个必填的参数,这是一个回调函数,用于构建目标页面的Widget;
  • super.settings:设置对象,用于定义路由的一些元数据,如路由的名字、是否为初始路由等;
  • this.maintainState:入栈新路由时,是否保留在内存的原来路由,设置为false则在路由没用时释放其占用的所有内存;
  • super.fullscreenDialog:表示新的路由是否为一个全屏的模糊对话框,在IOS中,设置为true,则新页面会从屏幕底部划入,而不是在水平方向;
  • super.allowSnapshotting:表示是否允许快照,快照是指当页面不在前台是保留其状态,以便在回到前台是快速恢复;
  • super.barrierDismissible:表示是否可以通过点击页面之外的区域来关闭对话框,对于非全屏的路由,这个属性通常用于控制是否可以点击背景来关闭对话框。
1.2 Navigator
  • 常用方法:
    • push(Route route):向 Navigator 的页面堆栈中添加一个新的页面。这通常用于启动一个新的活动或显示一个对话框;
    • pop([dynamic result]):从 Navigator 的页面堆栈中移除顶部的页面并返回到前一个页面。result 参数是可选的,可以用来传递结果数据给前一个页面;

2、路由传值

2.1 创建TipRoute路由

这个路由的功能是接受一个提示文本参数,并将参数显示在页面上,点击返回按钮后会向上一个页面带上一个返回参数。

class TipRoute extends StatelessWidget {
  const TipRoute({super.key, required this.text});

  final String text;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("提示"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(18),
        child: Center(
          child: Column(
            children: [
              Text(text),
              ElevatedButton(
                onPressed: () => Navigator.pop(context, "返回值"),
                child: const Text("返回"),
              )
            ],
          ),
        ),
      ),
    );
  }
}
2.2 RouteTestRoute

返回到该页面时,调试模式下打印上个页面携带的参数

class RouteTestRoute extends StatelessWidget {
  const RouteTestRoute({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          title: const Text("路由传值"),
        ),
        body: Center(
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
              ElevatedButton(
                  onPressed: () async {
                    // 打开TipRoute,并等待返回结果
                    var result = await Navigator.push(context,
                        MaterialPageRoute(builder: (context) {
                      //路由参数
                      return const TipRoute(text: "提示");
                    }));
                    // 是否为调试模式,是的话为true,不是的话为false
                    if (kDebugMode) {
                      print("路由返回值:$result");
                    }
                  },
                  child: const Text("打开提示页")),
              // ElevatedButton(
              //   onPressed: () {
              //     Navigator.push(context, MaterialPageRoute(builder: (context) {
              //       return const MyHomePage(title: "主页");
              //     }));
              //   },
              //   child: const Text("返回主页"),
              // )
            ])));
  }
}

3、路由命名

我们可以通过给路由起一个名字,通过这个名字直接打开路由,这使得路由管理更加直观、简单。

3.1 路由表

使用命名路由的前提是提供并注册一个路由表,为名字与路由组件提供对应关系,注册路由表的过程就是为路由起一个名字

  • 定义:Map<String, WidgetBuilder> routes;

它是一个Map,Key为路由的名字,是一个字符串;

value是builder回调函数,用于生成对应路由的Widget;

应用会根据路由名字在路由表中查到对应的WidgetBuilder回调函数,然后调用该回调函数生成Widget并返回。

3.2 注册路由表

使用inititalRoute设置初始路由,并使用后routes注册路由表。更改后要热重载一下,避免页面报错。

class MyApp extends StatelessWidget {
  /* 声明了一个常量构造函数,它使得Flutter在编译时就确定MyApp对象的结构,而不是在运行时动态创建。
  这可以提升一定性能,在编译时优化常量对象的创建过程,并且在多个地方使用同一个常量对象时可以减少内存使用。
   */
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      // 应用名称
      title: 'Flutter Demo',
      initialRoute: "/",  // 初始路由
      // 主题设置
      theme: ThemeData(
        // 主题颜色
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        // 是否使用Material 3的设计规范
        useMaterial3: true,
      ),
      // 注册路由表
      routes: {
        "/": (context) => const MyHomePage(title: 'Flutter Demo Home Page'),  // 主页路由
        "new_page": (context) => const NewRoute(),  // 新路由
      },
      // 应用首页路由
      // home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
3.3 通过路由名打开新页面

通过路由名打开新路由有两种方式:

  • Navigator.pushNamed:它是最常用的导航发生它允许通过指定的名称打开一个新页面,适合在已经注册名称的情况下使用;
  • Navigator.pushNamedAndRemoveUntil:除了导航到指定的路由外,还会移除直到目标路由的所有页面,非常适合导航到新页面时清除所有中间的页面历史,比如登录后的主页面导航。
// 示例
ElevatedButton(
    style: ButtonStyle(
        foregroundColor: WidgetStateProperty.all(Colors.cyan)),
    onPressed: () {
      Navigator.pushNamed(context, "new_page");
      // Navigator.push(context, MaterialPageRoute(builder: (context) {
      //   return const NewRoute();
      // }));
    },
    child: const Text("示例路由")),

更改示例路由的跳转方式,点击热重载,点击示例路由发现依然可以跳转新路由界面。

3.4 命名路由参数传递
3.4.1 传递参数

修改按钮点击的回调函数,在通过名字访问路由时传递一个arguments值。

ElevatedButton(
    style: ButtonStyle(
        foregroundColor: WidgetStateProperty.all(Colors.cyan)),
    onPressed: () {
      // 直接打开路由
      // Navigator.pushNamed(context, "new_page");
      // 打开路由时传递参数
      Navigator.of(context).pushNamed("new_page", arguments: "路由参数");
      // Navigator.push(context, MaterialPageRoute(builder: (context) {
      //   return const NewRoute();
      // }));
    },
    child: const Text("示例路由")),
3.4.2 接收参数

在名字对应的路由中接收参数并将参数打印在控制台上。

// 新路由示例
class NewRoute extends StatelessWidget {
  const NewRoute({super.key});

  
  Widget build(BuildContext context) {
    // 获取路由参数
    var args = ModalRoute.of(context)?.settings.arguments;
    if (kDebugMode) {
      print(args);

    }return Scaffold(
      appBar: AppBar(
        title: const Text("新页面"),
      ),
      body: const Center(
        child: Text("新路由"),
      ),
    );
  }
}
3.4.3 适配

在TipRoute类中,我们通过Navigator.pop(context, “返回值”)将参数传递给上一个页面,在不改变TipRoute源码的情况下适配并将该路由命名。


  Widget build(BuildContext context) {
    return MaterialApp(
        // 省略无关代码...
        
        // 提示页路由
        "tip2": (context){
          // 获取传递的参数
          final arguments  = ModalRoute.of(context)?.settings.arguments;
          final text = arguments is String ? arguments : '默认文本';
          return TipRoute(text: text);
        },
      },
      // 应用首页路由
      // home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

RouteTestRoute类直接修改按钮回调函数返回默认文本。

class RouteTestRoute extends StatelessWidget {
  const RouteTestRoute({super.key});

  
  Widget build(BuildContext context) {
      // 省略无关代码...
      ElevatedButton(
          onPressed: () async {
            Navigator.of(context).pushNamed("tip2");
          },

若页面接收传递参数,则显示传递参数。

// 新路由示例
class NewRoute extends StatelessWidget {
	// 省略无关代码...
    return Scaffold(
      appBar: AppBar(
        title: const Text("新页面"),
      ),
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushNamed("tip2", arguments: args);
              },
              child: const Text("新路由"))
        ],
      )),
    );
  }
}
3.5 路由生成钩子

假如我们要判断用户是否登录,每打开一个路由页都进行一次判断很麻烦,当Navigator.pushNamed打开命名路由时,如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件;如果没有注册,则会调用onGenerateRoute来生成路由,回调签名如为:Route< dynamic > onGenerateRoute(RouteSettings settings),有了onGenerateRoute回调,就可以十分容易的控制页面权限了,我们不使用路由表,而是提供一个onGenerateRoute回调,在该回调中进行统一的权限控制。

  // // 注册路由表
  // routes: {
  //   // 主页路由
  //   "/": (context) => const MyHomePage(title: 'Flutter Demo Home Page'),
  //   // 新路由
  //   "new_page": (context) => const NewRoute(),
  //   // 提示页路由
  //   "tip2": (context) {
  //     // 获取传递的参数
  //     final arguments = ModalRoute.of(context)?.settings.arguments;
  //     final text = arguments is String ? arguments : '默认文本';
  //     return TipRoute(text: text);
  //   },
  // },

  // 使用onGenerateRoute管理路由
  onGenerateRoute: (RouteSettings settings) {
    return MaterialPageRoute(builder: (context) {
      if (settings.name == "/") {
        return const MyHomePage(title: 'Flutter Demo Home Page');
      } else if (settings.name == "new_page") {
        return const NewRoute();
      } else if (settings.name == "tip2") {
        // 获取传递的参数
        final arguments = ModalRoute.of(context)?.settings.arguments;
        final text = arguments is String ? arguments : '默认文本';
        return TipRoute(text: text);
      }
      // 如果没有匹配路由则返回默认页面
      return const MyHomePage(title: 'Flutter Demo Home Page');
    });
  },