跳转到正文
YGJoon 技术博客

Flutter 路由参数传递详解

发布于:
·1.1k 字 · 约 3 分钟·作者:
构造函数直传、pushNamed 带 arguments、onGenerateRoute 集中解析:Flutter 页面间传参的三种姿势与取舍
Flutter 路由参数传递详解文章封面
目录

简介

在页面导航时,经常需要传递参数。Flutter 提供了多种参数传递方式,本文详细介绍这些方法。

基本参数传递

构造函数传参

// 定义页面
class DetailPage extends StatelessWidget {
  final String id;
  final String title;
  
  const DetailPage({
    Key? key,
    required this.id,
    required this.title,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('ID: $id')),
    );
  }
}

// 导航并传参
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(
      id: '123',
      title: 'Product Detail',
    ),
  ),
);

命名路由参数

// 注册路由
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/detail') {
      final args = settings.arguments as Map<String, dynamic>;
      return MaterialPageRoute(
        builder: (context) => DetailPage(
          id: args['id'],
          title: args['title'],
        ),
      );
    }
    return null;
  },
);

// 导航并传参
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {
    'id': '123',
    'title': 'Product Detail',
  },
);

类型安全的参数传递

使用泛型

class TypedPageRoute<T> extends MaterialPageRoute<T> {
  TypedPageRoute({
    required WidgetBuilder builder,
    required this.arguments,
  }) : super(builder: builder);
  
  final T arguments;
}

// 使用示例
class DetailArguments {
  final String id;
  final String title;
  
  DetailArguments({
    required this.id,
    required this.title,
  });
}

Navigator.push(
  context,
  TypedPageRoute<DetailArguments>(
    builder: (context) => DetailPage(),
    arguments: DetailArguments(
      id: '123',
      title: 'Product Detail',
    ),
  ),
);

完整示例

// 参数模型
class ProductArguments {
  final String id;
  final String title;
  final double price;
  final String imageUrl;
  
  const ProductArguments({
    required this.id,
    required this.title,
    required this.price,
    required this.imageUrl,
  });
  
  // 从 Map 创建实例
  factory ProductArguments.fromMap(Map<String, dynamic> map) {
    return ProductArguments(
      id: map['id'] as String,
      title: map['title'] as String,
      price: map['price'] as double,
      imageUrl: map['imageUrl'] as String,
    );
  }
  
  // 转换为 Map
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'price': price,
      'imageUrl': imageUrl,
    };
  }
}

// 产品详情页
class ProductDetailPage extends StatelessWidget {
  final ProductArguments args;
  
  const ProductDetailPage({
    Key? key,
    required this.args,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(args.title)),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(
            args.imageUrl,
            width: double.infinity,
            height: 200,
            fit: BoxFit.cover,
          ),
          Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  args.title,
                  style: Theme.of(context).textTheme.headline6,
                ),
                SizedBox(height: 8),
                Text(
                  'Price: \$${args.price.toStringAsFixed(2)}',
                  style: Theme.of(context).textTheme.subtitle1,
                ),
                SizedBox(height: 8),
                Text('Product ID: ${args.id}'),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 路由配置
class AppRouter {
  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/product-detail':
        final args = settings.arguments as Map<String, dynamic>;
        final productArgs = ProductArguments.fromMap(args);
        return MaterialPageRoute(
          builder: (_) => ProductDetailPage(args: productArgs),
        );
      default:
        return MaterialPageRoute(
          builder: (_) => NotFoundPage(),
        );
    }
  }
}

// 使用示例
class ProductListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Products')),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('Product $index'),
            onTap: () {
              Navigator.pushNamed(
                context,
                '/product-detail',
                arguments: {
                  'id': 'PROD_$index',
                  'title': 'Product $index',
                  'price': 9.99 + index,
                  'imageUrl': 'https://picsum.photos/200?$index',
                },
              );
            },
          );
        },
      ),
    );
  }
}

最佳实践

  1. 必传参数走构造器:DetailPage({required this.id})——类型安全、IDE 可跳转、编译期检查,这是首选
  2. Get.arguments 只做”可选附加信息”:它是 dynamic,取出来必须判空+转类型(实战案例里 as String 在 null 上炸的坑)
  3. 深链接/query 参数用 Get.parameters:/detail?id=123 这种,用 Get.parameters['id'] 取,别手写字符串 split
  4. 需要序列化的参数(分享链接、通知跳转)实现 toJson/fromJson:query 只能传字符串,对象先序列化再拼

注意事项

  1. 别传大对象:列表页别把整个 item 传给详情页,传 id,详情页自己查缓存/调接口——大对象走路由参数,内存里多份拷贝
  2. 别传敏感信息:token、密码别放路由参数(Get.parameters 会进 URL,深链接可被系统日志记录)
  3. 参数为 null 要有兜底:详情页 id 为 null 时显示”参数错误”页,别直接 id! 炸掉
  4. 跨页面共享的”状态”别用参数传递:用状态管理(GetX controller/Provider);参数是一次性的,状态是持续的——混用会导致返回上一页数据不同步

实战案例

GetX 项目里我传参主要用两种。简单场景直接构造器传:Get.to(() => const CustomServicePage()),参数在构造时就定了,类型安全,IDE 能检查。需要跨页面共享的(比如从列表进详情要带 id),我用 Get.toNamed(RouteNames.cargoDetail, arguments: {'id': id}),目标页用 Get.arguments 取。但有个坑:Get.arguments 是 dynamic,取出来一定要判空+转类型,我吃过 arguments['id'] as String 在 null 上炸的亏。现在我的习惯是:页面间传参优先构造器,arguments 只做”可选的附加信息”,并且取的时候写防御代码。类型安全这事,编译器能帮你的,别推给运行时。

总结

合理的参数传递机制可以让页面之间的数据交互更加清晰和安全。通过使用类型安全的参数模型和良好的错误处理,可以提高应用的稳定性和可维护性。


分享这篇文章:


相关文章

上一篇
Flutter 命名路由详解
下一篇
Flutter SharedPreferences 详解