欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Flutter 页面跳转和传值的实现,

来源: 开发者 投稿于  被查看 13129 次 评论:37

Flutter 页面跳转和传值的实现,


目录
  • 一、页面跳转
    • 1.基本页面跳转
    • 2.命名路由和路由表
  • 二、页面传值
    • 1.push时向新页面传递数据
    • 2.pop时返回数据给前一个页面
  • 三、路由生成钩子(onGenerateRoute)
    • 四、路由传值的安全性
      • 1.验证传入数据
      • 2.处理空值和异常
    • 五、使用 Provider 管理跨页面的状态
      • 1.使用 Provider 进行状态管理和传值
      • 2.完整的Provider例子

    一、页面跳转

    1.基本页面跳转

    Navigator 介绍

    在 Flutter 中,Navigator 是一个管理应用视图(页面)的组件,它使用栈(Stack)的方式来控制页面的切换。每当你跳转到一个新页面时,Navigator 会将新页面的 Route 压栈(push),当你返回到之前的页面时,它会将当前页面的 Route 出栈(pop)。

    为了使用 Navigator 进行页面跳转,我们需要使用 BuildContext,它表示当前 widget 在 widget 树中的位置。BuildContext 是用于与 Navigator 进行交互的必要参数。

    Navigator.push 方法

    Navigator.push 方法用于将新的 Route 压入栈中,从而导航到新页面。

    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => NewPage()),
    );

    或这种写法

    Navigator.push(context, MaterialPageRoute(
      builder: (context) {
        return NewPage();
      },
    ));

    Navigator.pop 方法

    Navigator.pop 方法用于将栈顶的 Route 弹出,返回到前一个页面。

    Navigator.pop(context);

    MaterialPageRoute 和页面跳转动画

    MaterialPageRoute 是一种模态路由,它会根据目标平台的规范,为页面切换提供适当的动画。在 Android 上,它通常是一个从屏幕底部向上滑入的动画,而在 iOS 上,它通常是一个从屏幕右侧滑入的动画。

    无参数页面跳转示例代码

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        title: 'Navigation Basics',
        home: HomePage(),
      ));
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Home Page'),
          ),
          body: Center(
            child: ElevatedButton(
              child: Text('Open New Page'),
              onPressed: () {
                // 使用 Navigator.push 方法来跳转到新页面
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => NewPage()),
                );
              },
            ),
          ),
        );
      }
    }
    
    class NewPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('New Page'),
          ),
          body: Column(
            children: [
              Text('Welcome to the new page!'),
              TextButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: Text("pop"))
            ],
          ),
        );
      }
    }

    2.命名路由和路由表

    命名路由介绍

    命名路由是一种用于管理页面导航的技术,它允许你为每个页面分配一个唯一的名称,并通过这些名称在应用程序中进行页面之间的导航。命名路由,由一对字符串(路由名称)和对应的屏幕(或称为页面/视图)组成。

    命名路由的好处

    • 提高代码可维护性命名路由使得路由和它们对应的屏幕解耦,这让查找和修改特定路由相关的代码变得更加容易。
    • 简化路由管理当应用的结构变得更为复杂时,使用命名路由可以帮助集中管理路由,而不是在代码中散布大量的 Navigator.push 和 MaterialPageRoute

    配置命名路由

    我们可以在 MaterialApp 的 routes 属性中定义所有的命名路由。routes 是一个 Map,它的键是字符串(路由的名称),而值是对应的构造器函数,返回相应的页面 Widget。

    MaterialApp(
        title: 'Navigation with Named Routes',
        // 初始路由,应用启动时加载的路由
        initialRoute: '/',
        // 定义命名路由
        routes: {
          '/': (context) => HomePage(),
          '/newPage': (context) => NewPage(),
          '/thirdPage': (context) => ThirdPage(),
        },
    )
    

    Navigator.pushNamed 方法

    要使用命名路由进行页面跳转,可以调用 Navigator.pushNamed 方法,并传入对应的路由名称。

    Navigator.pushNamed(context, '/newPage');

    Navigator.pop 方法

    Navigator.pop 方法用于将栈顶的 Route 弹出,返回到前一个页面。

    Navigator.pop(context);

    Navigator.popAndPushNamed 方法

    Navigator.popAndPushNamed 方法用于从当前页面返回到上一个页面,并立即导航到指定的命名路由。

    该方法的作用是先执行 Navigator.pop 方法返回到上一个页面,然后立即执行 Navigator.pushNamed 方法导航到新的命名路由。

    Navigator.popAndPushNamed(context, '/thirdPage');

    配置和使用命名路由示例代码

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        title: 'Navigation with Named Routes',
        // 初始路由,应用启动时加载的路由
        initialRoute: '/',
        // 定义命名路由
        routes: {
          '/': (context) => HomePage(),
          '/newPage': (context) => NewPage(),
          '/thirdPage': (context) => ThirdPage(),
        },
      ));
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Home Page'),
          ),
          body: Center(
            child: ElevatedButton(
              child: Text('Open New Page'),
              // 使用命名路由进行页面跳转
              onPressed: () {
                Navigator.pushNamed(context, '/newPage');
              },
            ),
          ),
        );
      }
    }
    
    class NewPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('New Page'),
          ),
          body: Column(
            children: [
              Text('Welcome to the new page!'),
              TextButton(
                  onPressed: () {
                    Navigator.popAndPushNamed(context, '/thirdPage');
                  },
                  child: Text("popAndPushNamed"))
            ],
          ),
        );
      }
    }
    
    class ThirdPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Third Page'),
          ),
          body: Column(
            children: [
              Text('Welcome to the Third page!'),
              TextButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: Text("pop"))
            ],
          ),
        );
      }
    }

    二、页面传值

    1.push时向新页面传递数据

    (1).通过构造函数传递数据

    最直接的方式是通过目标页面的构造函数直接传递数据。

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        home: HomePage(),
      ));
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Home Page'),
          ),
          body: Center(
            child: ElevatedButton(
              child: Text('Pass Data to New Page'),
              onPressed: () {
                // 通过构造函数直接传递数据
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => NewPage(data: 'Hello from Home Page!'),
                  ),
                );
              },
            ),
          ),
        );
      }
    }
    
    class NewPage extends StatelessWidget {
      final String data;
    
      // 接收数据的构造函数
      NewPage({required this.data});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('New Page'),
          ),
          body: Center(
            child: Text(data), // 显示传递过来的数据
          ),
        );
      }
    }

    (2).使用 MaterialPageRoute 的 arguments 属性

    另一种传递数据的方式是使用 MaterialPageRoute 的 arguments 属性,这在使用命名路由时尤其有用。

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        // 初始路由,应用启动时加载的路由
        initialRoute: '/',
        // 定义命名路由
        routes: {
          '/': (context) => HomePage(),
          '/newPage': (context) => NewPage(),
        },
      ));
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Home Page'),
          ),
          body: Center(
            child: ElevatedButton(
              child: Text('Pass Data to New Page'),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/newPage',
                  arguments: 'Hello from Home Page!',
                );
              },
            ),
          ),
        );
      }
    }
    
    class NewPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 获取传递过来的数据
        final String data = ModalRoute.of(context)!.settings.arguments as String;
    
        return Scaffold(
          appBar: AppBar(
            title: Text('New Page'),
          ),
          body: Center(
            child: Text(data), // 显示传递过来的数据
          ),
        );
      }
    }

    2.pop时返回数据给前一个页面

    使用 Navigator.pop 返回结果

    当从一个页面返回到前一个页面时,可以通过 Navigator.pop 方法返回数据:

    // 假设这是 NewPage 中的一个按钮,当点击时返回数据给前一个页面
    ElevatedButton(
      onPressed: () {
        Navigator.pop(context, 'Result from New Page');
      },
      child: Text('Return Data to Home Page'),
    ),

    Navigator.push 和 await 结合使用

    你可以使用 await 关键字等待一个页面返回结果:

    // ... HomePage 中的按钮点击事件
    onPressed: () async {
      final result = await Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => NewPage()),
      );
    
      // 使用 ScaffoldMessenger 显示返回的结果
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(result?.toString() ?? 'No result')),
      );
    },

    onPressed: () async {
        final result = await Navigator.pushNamed(
          context,
          '/newPage',
          arguments: 'Hello from Home Page!',
        );
    
        // 使用 ScaffoldMessenger 显示返回的结果
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(result?.toString() ?? 'No result')),
        );
      },
    ),

    使用 PopScope 拦截系统返回按钮的行为

    如果你不显式的调用Navigator.pop(context, 'xxx'),就拿不到回传结果。比如你从系统导航上点击返回按钮,就没数据传递回去。

    如果一定任何返回都回传值,就需要定义导航栏,或者通过使用 PopScope widget 来拦截系统返回按钮的行为,并执行自定义的操作。

    class NewPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('New Page'),
          ),
          body: PopScope(
            canPop: false, // 使用canPop提前禁用pop
            onPopInvoked: (bool didPop) {
              // canPop被设置为false时。didPop参数表示返回导航是否成功。
              // `didPop`参数会告诉你路由是否成功地pop出
              if (!didPop) {
                // 在这里执行你的操作,比如返回数据. 前面不禁用pop的话,这里就会pop两次了。
                Navigator.pop(context, 'Custom back button result');
              }
            },
            child: Column(
              children: [
                TextButton(
                    onPressed: () {
                      Navigator.pop(context, 'Result from New Page');
                    },
                    child: Text("pop"))
              ],
            ),
          ),
        );
      }
    }

    注意:这里用多个地方调用Navigator.pop,从不同地方返回时,回传的值也会不同。如果要求回传的数据一致,就将Navigator.pop方法抽离放到一个方法中,多个返回位置调用同一个方法回传同样的数据。

    三、路由生成钩子(onGenerateRoute)

    在Flutter中,onGenerateRoute是一个非常强大的钩子,允许开发者对路由进行自定义操作。它在MaterialAppCupertinoApp中定义,并在导航到命名路由时被调用,特别是当使用Navigator.pushNamed时。它可以用于动态生成路由,传递参数到新页面,甚至处理未知的路由。

    下面是一个使用onGenerateRoute的示例,其中包括了处理动态路由和传递参数到未知页面的代码:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          // 应用初始路由
          initialRoute: '/',
          // onGenerateRoute 用于处理动态路由
          onGenerateRoute: (RouteSettings settings) {
            // 获取传递过来的参数,如果参数为null,则提供一个空的Map
            final arguments = settings.arguments as Map<String, dynamic>? ?? {};
    
            // 根据 settings.name 处理不同路由
            switch (settings.name) {
              case '/':
                return MaterialPageRoute(builder: (context) => HomePage());
              case '/details':
                // 假设 DetailsPage 接受一个 'data' 参数
                final String data = arguments['data'] as String? ?? '默认值';
                return MaterialPageRoute(builder: (context) => DetailsPage(data: data));
              default:
                // 如果没有匹配的路由,返回到一个未知页面路由
                return MaterialPageRoute(builder: (context) => UnknownPage());
            }
          },
        );
      }
    }
    
    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      String _data = "缺省值";
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('首页'),
          ),
          body: Column(
            children: [
              Text(_data), // 显示传递到本页面的数据
              ElevatedButton(
                onPressed: () async {
                  // 导航到详情页,并传递数据。同时,使用await等待详情页返回的结果
                  final result = await Navigator.pushNamed(
                    context,
                    '/details',
                    arguments: {'data': '这是一个秘密信息!'},
                  );
                  final arguments = result as Map<String, dynamic>? ?? {};
                  setState(() {
                    if (mounted) {
                      _data = arguments["data"] as String? ?? "";
                    }
                  });
                },
                child: Text('前往详情页'),
              ),
            ],
          ),
        );
      }
    }
    
    class DetailsPage extends StatelessWidget {
      final String data;
    
      DetailsPage({required this.data});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('详情页'),
          ),
          body: Column(
            children: [
              Text(data), // 显示传递到本页面的数据
              TextButton(
                  onPressed: () {
                    // 回传数据数据
                    Navigator.pop(context, {'data': '详情返回数据'});
                  },
                  child: Text("pop"))
            ],
          ),
        );
      }
    }
    
    class UnknownPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('未知页面'),
          ),
          body: Center(
            child: Text('该路由名称不存在。'),
          ),
        );
      }
    }

    四、路由传值的安全性

    1.验证传入数据

    当通过路由传递数据时,重要的是要验证接收的数据是否符合预期。你可以使用类型检查、正则表达式或自定义验证函数来确保数据的有效性和安全性。

    bool isValidData(dynamic data) {
      // 在这里添加验证逻辑,例如类型检查、内容检查等
      return data is String && data.isNotEmpty;
    }

    在使用数据之前,你可以调用这个函数来验证:

    if (isValidData(receivedData)) {
      // 数据有效,可以安全使用
    } else {
      // 数据无效,可以抛出异常或进行错误处理
    }

    2.处理空值和异常

    处理空值和异常是确保应用程序稳定性的重要部分。当你从路由接收数据时,应该始终假设这些数据可能为空或者不是预期的格式。下面是一些处理这些情况的策略:

    处理空值

    当你期望的数据可能为空时,可以使用Dart的null-aware运算符来优雅地处理:

    String data = receivedData ?? '默认值';

    或者在使用之前检查数据是否为null:

    if (receivedData != null) {
      // 使用 receivedData
    } else {
      // 处理空值情况,例如返回错误提示或设置默认值
    }

    异常处理

    如果数据转换或验证过程中可能抛出异常,你应该使用try-catch语句来捕获这些异常:

    try {
      // 尝试使用 receivedData
    } catch (e) {
      // 处理异常,例如记录日志、显示错误信息等
    }

    五、使用 Provider 管理跨页面的状态

    Provider 是一个流行的状态管理库,它依赖于 Flutter 的 InheritedWidget 来向下传递数据。它能够让你在 widget 树中跨越多个层级来传递和修改数据,而无需手动传递回调或数据。

    使用 Provider,你可以在应用的顶层提供一个状态,然后在应用的任何其他部分访问或修改这个状态。这适用于跨多个页面传递数据,甚至是整个应用的状态管理。

    详细使用参见另一文https://www.jb51.net/program/319047n8q.htm

    1.使用 Provider 进行状态管理和传值

    首先,你需要在 pubspec.yaml 文件中添加 provider 依赖项:

    dependencies:
      flutter:
        sdk: flutter
      provider: ^6.1.2 # 使用适合你的版本

    然后,在应用顶层(即要包裹住MaterialApp)引入 Provider

    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() {
      runApp(
        // 通过 MultiProvider 可以提供多个对象
        MultiProvider(
          providers: [
            // ChangeNotifierProvider 是 Provider 的一种,它可以响应通知
            ChangeNotifierProvider(create: (context) => DataProvider()),
          ],
          child: MyApp(),
        ),
      );
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          home: HomePage(),
        );
      }
    }
    // 定义一个继承自 ChangeNotifier 的数据模型,用来传递和响应变化
    class DataProvider extends ChangeNotifier {
      String _data = "初始数据";
    
      String get data => _data;
    
      void setData(String newData) {
        _data = newData;
        notifyListeners(); // 当更新数据时,通知监听的 widgets 进行重建
      }
    }

    HomePage 中的按钮点击时,可以使用 Provider 来更新数据:

    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 使用 Provider.of 来获取最近的 DataProvider 实例
        final dataProvider = Provider.of<DataProvider>(context);
    
        return Scaffold(
          appBar: AppBar(
            title: Text('首页'),
          ),
          body: Center(
            child: ElevatedButton(
              onPressed: () {
                // 更新数据
                dataProvider.setData('更新的数据');
                // 导航到详情页
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => DetailsPage()),
                );
              },
              child: Text('前往详情页并传递数据'),
            ),
          ),
        );
      }
    }
    
    class DetailsPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 监听 DataProvider,当数据变化时重建这个 widget
        final data = Provider.of<DataProvider>(context).data;
    
        return Scaffold(
          appBar: AppBar(
            title: Text('详情页'),
          ),
          body: Center(
            // 显示从 Provider 获取的数据
            child: Text(data),
          ),
        );
      }
    }

    2.完整的Provider例子

    下面是一个使用Provider进行状态管理和跨页面传值的完整示例,包括异常处理和空值检查:

    首先,确保已经添加了provider依赖。

    // main.dart
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider<DataModel>(
          create: (_) => DataModel(),
          child: MaterialApp(
            title: 'Flutter Demo',
            home: HomePage(),
          ),
        );
      }
    }
    
    class DataModel extends ChangeNotifier {
      String _data = '';
    
      String get data => _data;
    
      void updateData(String newData) {
        if (newData.isNotEmpty) {
          _data = newData;
          notifyListeners();
        } else {
          throw Exception('Data cannot be empty');
        }
      }
    }
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Home')),
          body: Center(
            child: Consumer<DataModel>(
              builder: (context, dataModel, child) {
                return ElevatedButton(
                  onPressed: () {
                    try {
                      dataModel.updateData('New Data from Home');
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => DetailsPage()),
                      );
                    } catch (e) {
                      // Handle the exception
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text(e.toString())),
                      );
                    }
                  },
                  child: Text('Go to Details'),
                );
              },
            ),
          ),
        );
      }
    }
    
    class DetailsPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Details')),
          body: Center(
            child: Consumer<DataModel>(
              builder: (context, dataModel, child) {
                return Text(dataModel.data);
              },
            ),
          ),
        );
      }
    }

    在这个例子中,DataModel 是一个简单的数据持有类,它通过Provider允许在应用程序的其他部分访问和修改数据。HomePage 设置新的数据,并导航到DetailsPageDetailsPage 显示当前的数据。如果尝试设置空的数据,DataModel 将抛出一个异常,该异常在HomePage中被捕获并显示为一个SnackBar

    到此这篇关于Flutter 页面跳转和传值的实现的文章就介绍到这了,更多相关Flutter 页面跳转和传值内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

    您可能感兴趣的文章:
    • Flutter路由跳转参数处理技巧详解
    • flutter 路由跳转的实现示例
    • Flutter 使用Navigator进行局部跳转页面的方法
    • Flutter路由的跳转、动画和传参详解(最简单)
    • Flutter页面传值的几种方式

    用户评论