flutter3.0学习笔记

路由管理

Preview
  • 路由管理
  • Navigator
  • 命名路由(定义路由)
  • 注册路由
  • 通过路由名打开新路由页
  • 命名路由参数传递
  • 路由生成钩子
  • onGenerateRoute

路由管理

管理多个页面时有两个核心概念和类:Route和 Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。

Navigator

Navigator通过一个栈来管理活动路由集合。Navigator通过路由访问BuildContext并调用命令式方法,例如push() 或者 pop()。

  • Future push(BuildContext context, Route route)

Navigator.push 用来执行 Route 的入栈操作,就可以通过指定的 Route 跳转到对应的页面了,我们可以自己创建Route或者直接使用MaterialPageRoute

  • bool pop(BuildContext context, [ result ])

Navigator.pop 用来执行 Route 的退栈操作,即页面回退,可以添加可选参数 result 作为页面返回时携带的参数。

首先我们来看路由是怎么跳转的,新建一个路由文件RoutePage.dart

import 'package:flutter/material.dart';

class RoutePage extends StatelessWidget {
  const RoutePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('router test'),),
      body:  Center(
        child: Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children:  [
              const Text('this is router test page'),
              TextButton(
                  onPressed: (){
                    Navigator.pop(context);//返回上一页
                  },
                  child: const Text('go back'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

main.dart实例中, _MyHomePageState.build方法中的Column的子widget中添加一个按钮(TextButton),写按钮点击事件,导入RoutePage.dart文件

import 'package:learn_f3/RoutePage.dart';

...//
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    ... //省略无关代码
    TextButton(
      child: Text('open new RoutePage'),
      onPressed: () {
        //导航到新路由   
        Navigator.push( 
          context,
          MaterialPageRoute(builder: (context) {
            return RoutePage();
          }),
        );
      },
    ),
  ],
 )

点击按钮,路由就会跳转

image.png

命名路由(定义路由)

所谓“命名路由”(Named Route)即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式。

注册路由

initialRoute 属性定义了应用应该从哪个路由启动。 routes 属性定义了所有可用的命名路由,以及当我们跳转到这些路由时应该构建的 widgets。在原来的计时器页面上,通过为 MaterialApp 的构造函数额外的属性: initialRouteroutes 来定义我们的路由。

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  initialRoute: '/',//名为"/"的路由作为应用的home(首页)
  //注册路由
  routes: {
    '/':(context)=>const MyHomePage(title: 'Flutter Demo Home Page'),//注册首页路由
    'route_page':(context)=>const RoutePage(),
  },
  //home: MyHomePage(title: 'Flutter Demo Home Page'),
);
通过路由名打开新路由页

要通过路由名称来打开新路由,可以使用NavigatorpushNamed方法

  • Navigator.pushNamed()
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    ... //省略无关代码
    TextButton(
      child: Text('open new RoutePage'),
      onPressed: () {
        //打开新路由页
        Navigator.pushNamed(context, 'route_page');
      },
    ),
  ],
 )
命名路由参数传递
  1. 之前注册的路由:
 routes: {
    'route_page':(context)=>const RoutePage(),
  },
  1. 在打开路由时传递参数
Navigator.of(context).pushNamed('route_page',arguments: 'hello RoutePage');           
  1. 在路由页通过RouteSetting对象获取路由参数:
class RoutePage extends StatelessWidget {
  const RoutePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    //获取路由参数
    var args = ModalRoute.of(context)?.settings.arguments;
    return Scaffold(
      appBar: AppBar(title: const Text('router test'),),
      body:  Center(
        child: Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children:  [
              Text('$args'),//显示参数
              //...
            ],
          ),
        ),
      ),
    );
  }
}

路由生成钩子

钩子就是类似模板方法,用于处理特定情况下的事件。通过设置onGenerateRoute,来取代路由表,实现跳转前的验证功能。 使用场景:假如我们想在用户没有登录的时候自动跳转到登录页面,在登录的时候可以正常浏览其他页面。这样每一次打开页面的时候都去判断登录状态非常麻烦,我们可以利用MaterialApponGenerateRoute属性。

onGenerateRoute
  • onGenerateRoute只会对命名路由生效
  • onGenerateRoute属性,它在打开命名路由时可能会被调用
  • 如果指定的路由名在路由表中已注册,则会调用路由表中的builder函数来生成路由组件
  • 如果路由表中没有注册,才会调用onGenerateRoute来生成路由
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == PassArgumentsScreen.routeName) {
      final args = settings.arguments as ScreenArguments;
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
    assert(false, 'Need to implement ${settings.name}');
    return null;
  },
)

具体例子

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        ExtractArgumentsScreen.routeName: (context) =>
        const ExtractArgumentsScreen(),
      },
      onGenerateRoute: (settings) {
        if (settings.name == PassArgumentsScreen.routeName) {
          final args = settings.arguments as ScreenArguments;
          return MaterialPageRoute(
            builder: (context) {
              return PassArgumentsScreen(
                title: args.title,
                message: args.message,
              );
            },
          );
        }
        assert(false, 'Need to implement ${settings.name}');
        return null;
      },
      title: 'Navigation with Arguments',
      home: const HomeScreen(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  ExtractArgumentsScreen.routeName,
                  arguments: ScreenArguments(
                    'Extract Arguments Screen',
                    'This message is extracted in the build method.',
                  ),
                );
              },
              child: const Text('Navigate to screen that extracts arguments'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  PassArgumentsScreen.routeName,
                  arguments: ScreenArguments(
                    'Accept Arguments Screen',
                    'This message is extracted in the onGenerateRoute '
                        'function.',
                  ),
                );
              },
              child: const Text('Navigate to a named that accepts arguments'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as ScreenArguments;

    return Scaffold(
      appBar: AppBar(
        title: Text(args.title),
      ),
      body: Center(
        child: Text(args.message),
      ),
    );
  }
}
class PassArgumentsScreen extends StatelessWidget {
  static const routeName = '/passArguments';

  final String title;
  final String message;
  
  const PassArgumentsScreen({
    super.key,
    required this.title,
    required this.message,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text(message),
      ),
    );
  }
}

class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}