Flutter 页面跳转和传值的实现

 更新时间:2024年04月01日 11:41:31   作者:风浅月明  
跳转传值是再普通不过的小功能了,在开发中会经常用到,比如列表进入详情,本文主要介绍了Flutter 页面跳转和传值的实现,具有一定的参考价值,感兴趣的可以了解一下

一、页面跳转

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 页面跳转和传值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论