第三方选择器:flutter_picker_plus

入门案例

简介

flutter_picker_plus 是一个在 Flutter 中用于替代或增强原生选择器(Picker)的第三方库,它基于早期的 flutter_picker 做了许多改进与优化。

主要特性包括:

  • 多种Picker类型:NumberPickerDateTimePickerArrayPickerLinkage / 级联 Picker 等。
  • 国际化/多语言支持(支持 20+ 语言)
  • 多种展示模式:模态底部弹出(modal)、对话框(dialog)、嵌入式(embedded)等。
  • 高度可定制:可以自定义样式、颜色、布局、分隔符、头部、确认/取消按钮样式等等。
  • 支持联动(Linkage):可以创建多个列之间有关联的数据(例如省市区三级联动)
  • 支持大数据集:据称在性能方面进行了优化以处理较大数据集。

安装

flutter pub add flutter_picker_plus

案例

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_picker_plus/flutter_picker_plus.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: PickerDemoPage(),
    );
  }
}

class PickerDemoPage extends StatefulWidget {
  const PickerDemoPage({super.key});

  @override
  State<PickerDemoPage> createState() => _PickerDemoPageState();
}

class _PickerDemoPageState extends State<PickerDemoPage> {
  String _selectedValue = "未选择";

  void _showDateTimePicker() {
    Picker(
      adapter: DateTimePickerAdapter(
        type: PickerDateTimeType.kYMDHM, // 年月日时分
        isNumberMonth: true, // 月份数字显示
      ),
      title: const Text("请选择日期时间"),
      onConfirm: (Picker picker, List value) {
        DateTime dateTime = (picker.adapter as DateTimePickerAdapter).value!;
        setState(() {
          _selectedValue = dateTime.toString();
        });
      },
    ).showDialog(context); // 弹出对话框
  }

  void _showCityPicker() {
    final picker = Picker(
      adapter: PickerDataAdapter<String>(
        pickerData: JsonDecoder().convert('''
        [
          {"北京": ["东城", "西城", "朝阳"]},
          {"上海": ["黄浦", "徐汇", "浦东"]},
          {"广东": ["广州", "深圳", "珠海"]}
        ]
      '''),
      ),
      title: const Text("请选择省市区"),
      onConfirm: (picker, value) {
        setState(() {
          _selectedValue = picker.getSelectedValues().join(" - ");
        });
      },
    );

    // 使用 makePicker() 生成 Widget 并展示在对话框里
    showDialog(
      context: context,
      builder: (context) {
        return Dialog(
          child: SizedBox(
            height: 300,
            child: picker.makePicker(), // 直接作为 Widget 使用
          ),
        );
      },
    );
  }

  void _showSinglePicker() {
    Picker(
      adapter: PickerDataAdapter<String>(
        pickerData: ["苹果", "香蕉", "橙子", "葡萄", "西瓜"],
      ),
      title: Text("请选择水果"),
      onConfirm: (Picker picker, List value) {
        setState(() {
          _selectedValue = picker.getSelectedValues().join(" - ");
        });
      },
      cancelText: "返回", // 默认是 "Cancel",改成中文
      confirmText: "确定", // 默认是 "Confirm",改成中文
    ).showModal(context); // 底部弹出选择器
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("flutter_picker_plus Demo")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("选择结果:$_selectedValue", style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _showSinglePicker,
              child: const Text("选择水果"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _showDateTimePicker,
              child: const Text("选择日期时间"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _showCityPicker,
              child: const Text("选择省市区"),
            ),
          ],
        ),
      ),
    );
  }
}

Picker

基础属性

  • adapter:必填。决定选择器的数据来源和表现形式。

    • PickerDataAdapter:普通数据或多级联动数据。
    • DateTimePickerAdapter:日期时间选择器。
    • 还可以自定义 Adapter。
  • title:顶部标题区域的 Widget,一般是 Text("请选择...")

  • selecteds:初始选中的索引,比如 [0, 2] 代表第一列选中第0个,第二列选中第2个。

  • onConfirm
    用户点击确定按钮时回调,参数:

    onConfirm: (picker, values) {
      print(values);                // 选中的索引
      print(picker.getSelectedValues()); // 选中的值
    }
    
  • onCancel
    用户点击取消时回调。

  • onSelect
    滚动选择时实时触发(值变化时)。

UI 控制

  • height
    整个 Picker 的高度,默认 200。

  • itemExtent
    每一行的高度,默认 36。

  • hideHeader
    是否隐藏头部(标题 + 按钮),默认 false

  • backgroundColor
    背景颜色。

  • cancel / confirm:自定义取消和确认按钮的 Widget,例如:

    // 用 Text
    cancel: Text("返回", style: TextStyle(color: Colors.red)),
    confirm: Text("确定", style: TextStyle(color: Colors.blue)),
    
    // 用按钮
      cancel: TextButton(
        onPressed: () => Navigator.pop(context),
        child: const Text("返回", style: TextStyle(color: Colors.red)),
      ),
      confirm: TextButton(
        onPressed: () {
          picker.doConfirmAction(); // ✅ 可以访问外部定义的 picker
        },
        child: const Text("确定", style: TextStyle(color: Colors.blue)),
      ),
    

    注意:一旦你自定义了 cancel / confirm(无论是 TextContainerTextButton),库就 不会自动触发 onConfirm,所以点击“确定”按钮后不会执行回调。

  • cancelTextconfirmText

    • cancelText → 设置左侧按钮文字
    • confirmText → 设置右侧按钮文字
      cancelText: "返回",   // 默认是 "Cancel",改成中文
      confirmText: "确定",  // 默认是 "Confirm",改成中文
    
  • textStyle:普通项的文字样式。

  • selectedTextStyle:选中项的文字样式。

展示方式

  • showDialog(context):以弹窗方式显示。
  • showModal(context):从底部滑出的方式显示。
  • makePicker():返回一个 Widget,你可以放到 DialogBottomSheet 或页面里。

特殊属性(针对日期时间)

adapterDateTimePickerAdapter 时,还可以用:

  • type:控制时间显示的粒度,比如:
    • PickerDateTimeType.kYMD(年月日)
    • PickerDateTimeType.kYMDHM(年月日时分)
    • PickerDateTimeType.kYMDHMS(年月日时分秒)
  • isNumberMonth:月份是否显示为数字(true)还是文字(false)。
  • yearBegin / yearEnd:限定年份范围。

PickerDataAdapter

是什么

PickerDataAdapterflutter_picker_plus 中用来把原始数据(数组或多级数据)适配成 Picker 可识别的数据结构的适配器。

它本质上负责把普通的 ListList<PickerItem> 转换成 Picker 内部可以渲染的树形结构,并提供一些额外的选项,比如是否多级联动、是否是数组模式等。

主要构造函数

PickerDataAdapter<T>({
  List? pickerData,                // 原始数据:可以是 List、List<Map> 等
  List<PickerItem<T>>? data,       // 已经构造好的 PickerItem 列表
  this.isArray = false,            // 是否是数组模式(没有多级联动)
})

pickerData

  • 支持原始数组、嵌套 Map、List 等格式。
  • 适合从 JSON 直接生成 Picker。
  • 内部会被 _parseData 转换成 PickerItem 结构。
List data = [
  {"北京": ["东城", "西城"]},
  {"上海": ["浦东", "徐汇"]}
];
PickerDataAdapter(pickerData: data);

data

  • 如果你已经手动创建了 PickerItem 对象列表,也可以直接传给 data
  • 例如:
List<PickerItem<String>> items = [
  PickerItem(text: const Text("北京"), value: "北京"),
  PickerItem(text: const Text("上海"), value: "上海"),
];
PickerDataAdapter(data: items);

isArray

  • 布尔值,表示数据是否是数组模式。
  • true:数据按平级数组处理,不考虑多级联动。
  • false(默认):数据会按多级树状处理。

PickerItem

PickerItemflutter_picker_plus 中的数据单元,用于表示 Picker 中的每一个选项。它既可以是单级选项,也可以是多级(层级/子选项)结构的节点。

class PickerItem<T> {

  final Widget? text;

  final T? value;

  final List<PickerItem<T>>? children;

  PickerItem({this.text, this.value, this.children});
}

参数说明

  1. text
    • 类型:Widget?
    • 功能:在 Picker 中显示的内容。可以是 TextIcon、或者自定义 Widget。
    • 可选,但建议提供,如果没有提供 value,至少要有 text
  2. value
    • 类型:T?(泛型)
    • 功能:和这个 Picker 选项对应的真实数据。通过 picker.getSelectedValues() 可以拿到这个值。
    • 可选,如果没有提供 text,至少要有 value
    • 泛型 T 决定 value 类型,与 PickerDataAdapter<T> 保持一致。
  3. children
    • 类型:List<PickerItem<T>>?
    • 功能:子节点,用于多级联动 Picker(省-市-区等场景)。
    • 如果没有子节点,可以为 null

PickerDataAdapter的泛型

PickerDataAdapter<T> 的泛型 T 决定了 选中值的类型,也就是你通过 picker.getSelectedValues()PickerItem.value 拿到的数据类型。

泛型 T 的作用

  • T 表示每个选项的 value 类型
  • 当你定义 PickerItem<T> 时,其 value 就是 T 类型。
  • PickerDataAdapter<T> 会保证返回的选中值类型与 T 匹配,避免类型转换错误。

泛型用 String,因为数据是字符串。

Picker(
  adapter: PickerDataAdapter<String>(
    pickerData: ["苹果", "香蕉", "橙子"], // 值是 String
  ),
  onConfirm: (picker, value) {
    // 这里 getSelectedValues() 返回 List<String>
    List<String> selected = picker.getSelectedValues();
    print(selected); // ["苹果"]
  },
).showModal(context);

泛型用 int,因为数据是数字。

Picker(
  adapter: PickerDataAdapter<int>(
    pickerData: [1, 2, 3, 4, 5], // 值是 int
  ),
  onConfirm: (picker, value) {
    List<int> selected = picker.getSelectedValues(); // List<int>
    print(selected); // [3]
  },
).showModal(context);

泛型用 Fruit,这样选中的值就是你自定义对象。

class Fruit {
  final String name;
  final double price;
  Fruit(this.name, this.price);
}

Picker(
  adapter: PickerDataAdapter<Fruit>(
    data: [
      PickerItem(text: "苹果", value: Fruit("苹果", 3.5)),
      PickerItem(text: "香蕉", value: Fruit("香蕉", 2.0)),
    ],
  ),
  onConfirm: (picker, value) {
    List<Fruit> selected = picker.getSelectedValues(); // List<Fruit>
    print(selected[0].name); // 苹果
  },
).showModal(context);

案例

import 'package:flutter/material.dart';
import 'package:flutter_picker_plus/flutter_picker_plus.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const CityPickerDemo(),
    );
  }
}

class CityPickerDemo extends StatefulWidget {
  const CityPickerDemo({super.key});

  @override
  State<CityPickerDemo> createState() => _CityPickerDemoState();
}

class _CityPickerDemoState extends State<CityPickerDemo> {
  String _selectedCity = "未选择";

  void _showCityPicker() {
    Picker(
      adapter: PickerDataAdapter<String>(
        data: [
          PickerItem(
            value: "北京",
            text: const Text("北京"),
            children: [
              PickerItem(value: "东城", text: const Text("东城")),
              PickerItem(value: "西城", text: const Text("西城")),
              PickerItem(value: "朝阳", text: const Text("朝阳")),
            ],
          ),
          PickerItem(
            value: "上海",
            text: const Text("上海"),
            children: [
              PickerItem(value: "黄浦", text: const Text("黄浦")),
              PickerItem(value: "徐汇", text: const Text("徐汇")),
              PickerItem(value: "浦东", text: const Text("浦东")),
            ],
          ),
          PickerItem(
            value: "广东",
            text: const Text("广东"),
            children: [
              PickerItem(value: "广州", text: const Text("广州")),
              PickerItem(value: "深圳", text: const Text("深圳")),
              PickerItem(value: "珠海", text: const Text("珠海")),
            ],
          ),
        ],
      ),
      title: const Text("请选择省市区"),
      onConfirm: (Picker picker, List<int> value) {
        setState(() {
          _selectedCity = picker.getSelectedValues().join(" - ");
        });
      },
    ).showModal(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("PickerItem 多级选择器 Demo")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("选择结果:$_selectedCity", style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _showCityPicker,
              child: const Text("选择省市区"),
            ),
          ],
        ),
      ),
    );
  }
}

×

喜欢就点赞,疼爱就打赏