虽然学的慢了点,但是感觉最近收获满满,小鱼今天要开始二级页面啦😂。这就不得不说路由与导航,Flutter 通过 Route 定义页面,通过 Navigator 管理页面栈,实现类似 Android/iOS 的页面导航机制,Route(路由):页面的抽象表示,创建界面内容,接收传递的参数,响应导航器的操作;Navigator(导航器):路由栈的管理者,维护路由的入栈(打开)和出栈(关闭),提供丰富的路由管理方法。

PS: 文章以新创建的router项目为例

一、基础导航

使用 Navigator.push() 和 Navigator.pop()

1、基础用法

// 页面跳转
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => SecondPage(),
  ),
);

// 返回上一页
Navigator.of(context).pop();

// 带参数返回
Navigator.of(context).pop('返回值');

  

main.dart

import 'package:flutter/material.dart';

import 'SecondPage.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Router Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Router Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),

            TextButton(
              child: Text("Jump to second page"),
              onPressed: () {
                //导航到新路由
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) {
                      return SecondPage();
                    },
                  ),
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("SecondPage")),
      body: Center(
        child: TextButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text("This is second page,click for back"),
        ),
      ),
    );
  }
}

2、相关类详解

1)MaterialPageRoute

MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,并且定义了路由构建及切换时过渡动画的相关接口及属性。

MaterialPageRoute 是 Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:

  • 对于 Android,当打开新页面时,新的页面会从屏幕底部滑动到屏幕顶部;当关闭页面时,当前页面会从屏幕顶部滑动到屏幕底部后消失,同时上一个页面会显示到屏幕上。
  • 对于 iOS,当打开页面时,新的页面会从屏幕右侧边缘一直滑动到屏幕左边,直到新页面全部显示到屏幕上,而上一个页面则会从当前屏幕滑动到屏幕左侧而消失;当关闭页面时,正好相反,当前页面会从屏幕右侧滑出,同时上一个页面会从屏幕左侧滑入。
class MaterialPageRoute<T> extends PageRoute<T> with MaterialRouteTransitionMixin<T> {
  /// Construct a MaterialPageRoute whose contents are defined by [builder].
  MaterialPageRoute({
    required this.builder,
    super.settings,
    super.requestFocus,
    this.maintainState = true,
    super.fullscreenDialog,
    super.allowSnapshotting = true,
    super.barrierDismissible = false,
    super.traversalEdgeBehavior,
    super.directionalTraversalEdgeBehavior,
  }) {
    assert(opaque);
  }

...
}

builder 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。

settings 包含路由的配置信息,如路由名称、是否初始路由(首页)。

maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为 false

fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在 iOS 中,如果fullscreenDialogtrue,新页面将会从屏幕底部滑入(而不是水平方向)。

2)Navigator

Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈

Future push(BuildContext context, Route route)

将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。

bool pop(BuildContext context, [ result ])

将栈顶路由出栈,result 为页面关闭时返回给上一个页面的数据。

Navigator 还有很多其他方法,如Navigator.replaceNavigator.popUntil等如下示例

2、常用导航方法

// 1. push - 跳转到新页面
Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage()));

// 2. pop - 返回
Navigator.pop(context);

// 3. pushReplacement - 替换当前页面
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (_) => NewHomePage()),
);

// 4. pushAndRemoveUntil - 跳转并移除之前所有页面
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (_) => LoginPage()),
  (route) => false, // 移除所有路由
);

// 5. popUntil - 返回到指定页面
Navigator.popUntil(context, ModalRoute.withName('/home'));

二、命名路由

在 MaterialApp 中配置,便于管理

1、基本配置

MaterialApp(
  title: '路由示例',
  initialRoute: '/',
  routes: {
    '/': (context) => HomePage(),
    '/details': (context) => DetailsPage(),
    '/settings': (context) => SettingsPage(),
    '/profile': (context) => ProfilePage(),
  },
)

2、使用命名路由

// 跳转到命名路由
Navigator.pushNamed(context, '/details');

// 带参数跳转
Navigator.pushNamed(
  context,
  '/details',
  arguments: {'id': 123, 'name': '示例'},
);

// 替换当前路由
Navigator.pushReplacementNamed(context, '/details');

// 跳转并清除所有路由
Navigator.pushNamedAndRemoveUntil(
  context,
  '/login',
  (route) => false,
);

三、参数传递通过构造函数或 arguments 传递

1、传递参数

// 传递简单参数
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(
      id: 123,
      title: '示例标题',
    ),
  ),
);

// 传递复杂对象
class User {
  final String name;
  final int age;
  
  User(this.name, this.age);
}

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProfilePage(user: User('张三', 25)),
  ),
);

2、接收参数

// 在命名路由中接收参数
class DetailsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 从 arguments 获取参数
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
    final id = args['id'];
    final name = args['name'];
    
    return Scaffold(
      appBar: AppBar(title: Text('详情页')),
      body: Center(
        child: Text('ID: $id, Name: $name'),
      ),
    );
  }
}

// 在构造函数中接收参数
class ProfilePage extends StatelessWidget {
  final User user;
  
  const ProfilePage({super.key, required this.user});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用户资料')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('姓名: ${user.name}'),
            Text('年龄: ${user.age}'),
          ],
        ),
      ),
    );
  }
}

四、路由生成器

1、onGenerateRoute

MaterialApp(
  initialRoute: '/',
  onGenerateRoute: (settings) {
    // 处理动态路由
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => HomePage());
      case '/details':
        final args = settings.arguments as Map<String, dynamic>;
        return MaterialPageRoute(
          builder: (_) => DetailsPage(id: args['id']),
        );
      case '/user':
        final userId = settings.arguments as String;
        return MaterialPageRoute(
          builder: (_) => UserPage(userId: userId),
        );
      default:
        return MaterialPageRoute(
          builder: (_) => NotFoundPage(routeName: settings.name!),
        );
    }
  },
)

2、onUnknownRoute

MaterialApp(
  onUnknownRoute: (settings) {
    return MaterialPageRoute(
      builder: (_) => NotFoundPage(routeName: settings.name!),
    );
  },
)

3、路由拦截

class RouteGuard {
  static bool isAuthenticated = false;

  static Route<dynamic>? generateRoute(RouteSettings settings) {
    // 需要认证的路由
    final protectedRoutes = ['/profile', '/settings', '/dashboard'];
    
    if (protectedRoutes.contains(settings.name)) {
      if (!isAuthenticated) {
        // 未认证,跳转到登录页
        return MaterialPageRoute(
          builder: (_) => LoginPage(),
        );
      }
    }

    // 正常路由
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => HomePage());
      case '/login':
        return MaterialPageRoute(builder: (_) => LoginPage());
      case '/profile':
        return MaterialPageRoute(builder: (_) => ProfilePage());
      default:
        return MaterialPageRoute(
          builder: (_) => NotFoundPage(routeName: settings.name!),
        );
    }
  }
}

五、路由嵌套

1、Tab导航

class MainTabPage extends StatefulWidget {
  @override
  _MainTabPageState createState() => _MainTabPageState();
}

class _MainTabPageState extends State<MainTabPage> {
  int _currentIndex = 0;
  
  final List<Widget> _pages = [
    HomeTabPage(),
    DiscoverTabPage(),
    MessageTabPage(),
    ProfileTabPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}

2、Drawer 导航

class MainDrawerPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('主页面')),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            DrawerHeader(
              decoration: BoxDecoration(color: Colors.blue),
              child: Text('导航菜单', style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
            ListTile(
              leading: Icon(Icons.home),
              title: Text('首页'),
              onTap: () {
                Navigator.pop(context);
                Navigator.pushNamed(context, '/home');
              },
            ),
            ListTile(
              leading: Icon(Icons.settings),
              title: Text('设置'),
              onTap: () {
                Navigator.pop(context);
                Navigator.pushNamed(context, '/settings');
              },
            ),
            ListTile(
              leading: Icon(Icons.help),
              title: Text('帮助'),
              onTap: () {
                Navigator.pop(context);
                Navigator.pushNamed(context, '/help');
              },
            ),
          ],
        ),
      ),
      body: Center(child: Text('主要内容')),
    );
  }
}

六、路由动画

1、自定义动画

// 淡入淡出动画
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
    transitionDuration: Duration(milliseconds: 300),
  ),
);

// 滑动动画
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(1.0, 0.0);
      const end = Offset.zero;
      const curve = Curves.ease;
      
      var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
      var offsetAnimation = animation.drive(tween);
      
      return SlideTransition(
        position: offsetAnimation,
        child: child,
      );
    },
    transitionDuration: Duration(milliseconds: 400),
  ),
);

2、自定义路由类

class SlideRightRoute extends PageRouteBuilder {
  final Widget page;

  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
          ) =>
              page,
          transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
          ) =>
              SlideTransition(
            position: Tween<Offset>(
              begin: const Offset(-1, 0),
              end: Offset.zero,
            ).animate(animation),
            child: child,
          ),
        );
}

// 使用自定义路由
Navigator.push(context, SlideRightRoute(page: DetailPage()));

今天到此为止,下篇见!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐