Flutter 页面跳转和传值的实现

来自:网络
时间:2024-06-09
阅读:

一、页面跳转

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

返回顶部
顶部