flutter 修改选择器样式

闻人修平
2023-12-01

第三方库的选择器样式跟公司产品需求不一样,需要更改第三方库源码

flutter_picker: ^2.0.3

//SelectorReviseUtil

/**
 * @Description: 单列 多列 日期选择器
 * 修改右上角的确定按键,在选择器的下方添加确定回调按钮
 */

import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:social_im/common/colors.dart';
import 'package:social_im/pages/widget/myText.dart';
import 'package:social_im/pages/widget/pickingDialog.dart';
import 'package:social_im/pages/widget/splitLine.dart';

import '../../router.dart';

typedef _StringClickCallBack = void Function(int selectIndex, Object selectStr);
typedef _StringSelectClickCallBack = void Function(
    int selectIndex, Object selectStr, int index);

typedef _ArrayClickCallBack = void Function(
    List<int> selecteds, List<dynamic> strData);
typedef _DateClickCallBack = void Function(
    dynamic selectDateStr, dynamic selectData);

enum DateTypeSecond {
  YM, // y,m
  YMD, // y,m,d
  YMD_HM, //y,m,d,hh,mm
  YMD_AP_HM, //y,m,d,ap,hh,mm
}

class SecondPickerTool {
  //单列
  static void showStringPicker<T>(
      BuildContext context, {
        required List<T> data,
        required String title,
        required String cancelText,
        required String confirmText,
        required int normalIndex,
        PickerDataAdapter? adapter,
        required _StringClickCallBack clickCallBack,
      }) {
    openOneModalPicker(context,
        adapter: adapter ?? PickerDataAdapter(pickerdata: data, isArray: false),
        clickCallBack: (Pickering picker, List<int> selecteds) {
          clickCallBack(selecteds[0], data[selecteds[0]]!);
        },
        selecteds: [normalIndex],
        title: title,
        cancelText: cancelText,
        confirmText: confirmText);
  }

  // ///多列
  static void showArrayPicker<T>(
    BuildContext context, {
    required List<T> data,
    required String title,
    required String cancelText,
    required String confirmText,
    required List<int> normalIndex,
        PickerDataAdapter? adapter,
    required _ArrayClickCallBack clickCallBack,
  }) {
    openModalPicker(context,
        adapter: adapter ?? PickerDataAdapter(pickerdata: data, isArray: true), clickCallBack: (Pickering picker, List<int> selecteds) {
      clickCallBack(selecteds, picker.getSelectedValues());
    },
        selecteds: normalIndex,
        title: title,
        cancelText: cancelText,
        confirmText: confirmText);
  }

  ///日期选择器
  static void showDatePicker(
      BuildContext context, {
        required DateTypeSecond dateType,
        required String title,
        required String cancelText,
        required String confirmText,
        required DateTime maxValue,
        required DateTime minValue,
        DateTime? value,
        DateTimePickerAdapter? adapter,
        required _DateClickCallBack clickCallBack,
      }) {
    int timeType;
    if (dateType == DateTypeSecond.YM) {
      timeType = PickerDateTimeType.kYM;
    } else if (dateType == DateTypeSecond.YMD_HM) {
      timeType = PickerDateTimeType.kYMDHM;
    } else if (dateType == DateTypeSecond.YMD_AP_HM) {
      timeType = PickerDateTimeType.kYMD_AP_HM;
    } else {
      timeType = PickerDateTimeType.kYMD;
    }
    openModalPicker(context,
        adapter: adapter ??
            DateTimePickerAdapter(
                type: timeType,
                isNumberMonth: true,
                maxValue: maxValue,
                minValue: minValue,
                value: value ?? DateTime.now()),
        title: title,
        cancelText: cancelText,
        confirmText: confirmText,
        clickCallBack: (Pickering picker, List<int> selecteds) {
          var time = (picker.adapter as DateTimePickerAdapter).value;
          var timeStr;
          if (dateType == DateTypeSecond.YM) {
            timeStr = '${time?.year.toString()}${time?.month.toString()}';
          }
          if (dateType == DateTypeSecond.YMD) {
            timeStr =
            '${time?.year.toString()}${time?.month.toString()}${time?.day.toString()}';
          } else if (dateType == DateTypeSecond.YMD_HM) {
            timeStr =
            '${time?.year.toString()}${time?.month.toString()}${time?.day.toString()}${time?.hour.toString()}${time?.minute.toString()}';
          } else if (dateType == DateTypeSecond.YMD_AP_HM) {
            var str = formatDate(time!, [am]) == "AM" ? "上午" : "下午";
            timeStr =
            '${time.year.toString()}${time.month.toString()}${time.day.toString()}${time.hour.toString()}${time.minute.toString()}';
          } else {
            timeStr =
            '${time?.year.toString()}${time?.month.toString()}${time?.day.toString()}';
          }
          clickCallBack(timeStr, picker.adapter.text);
        }, selecteds: []);
  }

  static void openModalPicker(
      BuildContext context, {
        required PickerAdapter adapter,
        required String title,
        required String cancelText,
        required String confirmText,
        required List<int> selecteds,
        required PickerConfirmCallback clickCallBack,
      }) {
    Pickering picker = Pickering(
        adapter: adapter,
        backgroundColor: CommonColors.getColorFCFFFA,
        containerColor: CommonColors.getColorFCFFFA,
        headerColor: CommonColors.getColorFCFFFA,
        textAlign: TextAlign.left,
        itemExtent: 40,
        // 倾斜度
        diameterRatio: 5,
        //上下距离
        squeeze: 1,
        // columnPadding: EdgeInsets.only(bottom: 20),
        height: MediaQuery.of(context).size.height / 3,
        selecteds: selecteds,
        // hideHeader: true,
        // builderHeader: (BuildContext context) => Container(
        //     padding: const EdgeInsets.all(18),
        //     child: Row(children: [
        //       Container(
        //         padding: const EdgeInsets.only(
        //             right: 19, left: 19, top: 9, bottom: 9),
        //         child:
        //             ContentText(cancelText, 16.0, CommonColors.getColor27D6BC),
        //         decoration: BoxDecorationUtil().setLineBoxDecoration(
        //             Colors.white, 20.0, 1.0, CommonColors.getColor27D6BC),
        //       ),
        //       Expanded(
        //           child: Center(
        //         child: ContentText(title, 16.0, CommonColors.getColor1A1A1A),
        //       )),
        //       GestureDetector(
        //           onTap: () {
        //             clickCallBack;
        //             if (onConfirmBefore != null &&
        //                 !(await onConfirmBefore!(this, selecteds))) {
        //             return; // Cancel;
        //             }
        //             Navigator.pop<List<int>>(context, selecteds);
        //           },
        //           child: Container(
        //             padding: const EdgeInsets.only(
        //                 right: 19, left: 19, top: 9, bottom: 9),
        //             child: ContentText(confirmText, 16.0, Colors.white),
        //             decoration: BoxDecorationUtil().setFillBoxDecoration(
        //                 CommonColors.getColor27D6BC, 20.0),
        //           ))
        //     ])),
        //头背景
        columnPadding: EdgeInsets.only(top: 10),
        headerDecoration: BoxDecoration(
            border: Border(
                bottom: BorderSide(
                    color: CommonColors.getColorFCFFFA, width: 0.5))),
        // //选中框样式
        // selectionOverlay: Container(
        //   height: 40,
        //   color: const Color.fromARGB(30, 118, 118, 128),
        // ),
        title: ContentTextBlod(
            title, 18.0, CommonColors.getColor000000, FontWeight.w500),
        cancelText: cancelText,
        cancelTextStyle:
        TextStyle(color: CommonColors.getColor000000, fontSize: 16.0),
        confirmText: confirmText,
        confirmTextStyle:
        TextStyle(color: CommonColors.getColor000000, fontSize: 16.0),
        selectedTextStyle: TextStyle(
            color: CommonColors.getColor000000, fontWeight: FontWeight.w500,fontSize: 18),
        textStyle: TextStyle(color: CommonColors.getColor000000, fontSize: 16),
        onConfirm: clickCallBack);

    picker.showModal(context, backgroundColor: Colors.transparent,
        builder: (context, view) {
          return Material(
              color: CommonColors.getColorFCFFFA,
              borderRadius: const BorderRadius.only(
                  topLeft: Radius.circular(20), topRight: Radius.circular(20)),
              child:
              Container(padding: const EdgeInsets.only(top: 13), child: view));
        });
  }

  static void openOneModalPicker(
      BuildContext context, {
        required PickerAdapter adapter,
        required String title,
        required String cancelText,
        required String confirmText,
        required List<int> selecteds,
        required PickerConfirmCallback clickCallBack,
      }) {
    Pickering picker = Pickering(
        adapter: adapter,
        backgroundColor: CommonColors.getColorFCFFFA,
        containerColor: CommonColors.getColorFCFFFA,
        headerColor: CommonColors.getColorFCFFFA,
        textAlign: TextAlign.left,
        itemExtent: 40,
        // 倾斜度
        diameterRatio: 5,
        //上下距离
        squeeze: 1,
        // columnPadding: EdgeInsets.only(bottom: 20),
        height: MediaQuery.of(context).size.height / 3,
        selecteds: selecteds,
        // hideHeader: true,
        // builderHeader: (BuildContext context) => Container(
        //     padding: const EdgeInsets.all(18),
        //     child: Row(children: [
        //       Container(
        //         padding: const EdgeInsets.only(
        //             right: 19, left: 19, top: 9, bottom: 9),
        //         child:
        //             ContentText(cancelText, 16.0, CommonColors.getColor27D6BC),
        //         decoration: BoxDecorationUtil().setLineBoxDecoration(
        //             Colors.white, 20.0, 1.0, CommonColors.getColor27D6BC),
        //       ),
        //       Expanded(
        //           child: Center(
        //         child: ContentText(title, 16.0, CommonColors.getColor1A1A1A),
        //       )),
        //       GestureDetector(
        //           onTap: () {
        //             clickCallBack;
        //             if (onConfirmBefore != null &&
        //                 !(await onConfirmBefore!(this, selecteds))) {
        //             return; // Cancel;
        //             }
        //             Navigator.pop<List<int>>(context, selecteds);
        //           },
        //           child: Container(
        //             padding: const EdgeInsets.only(
        //                 right: 19, left: 19, top: 9, bottom: 9),
        //             child: ContentText(confirmText, 16.0, Colors.white),
        //             decoration: BoxDecorationUtil().setFillBoxDecoration(
        //                 CommonColors.getColor27D6BC, 20.0),
        //           ))
        //     ])),
        //头背景
        columnPadding: EdgeInsets.only(top: 10),
        headerDecoration: BoxDecoration(
            border: Border(
                bottom: BorderSide(
                    color: CommonColors.getColorFCFFFA, width: 0.5))),
        // //选中框样式
        // selectionOverlay: Container(
        //   height: 40,
        //   color: const Color.fromARGB(30, 118, 118, 128),
        // ),
        title: ContentTextBlod(
            title, 17.0, CommonColors.getColor000000, FontWeight.w500),
        cancelText: cancelText,
        cancelTextStyle:
        TextStyle(color: CommonColors.getColor000000, fontSize: 16.0),
        confirmText: confirmText,
        confirmTextStyle:
        TextStyle(color: CommonColors.getColor000000, fontSize: 16.0),
        selectedTextStyle: TextStyle(
            color: CommonColors.getColor000000, fontWeight: FontWeight.w500,fontSize: 18),
        textStyle: TextStyle(color: Colors.grey, fontSize: 16),
        onConfirm: clickCallBack);

    picker.showModal(context, backgroundColor: Colors.transparent,
        builder: (context, view) {
          return Material(
              color: CommonColors.getColorFCFFFA,
              borderRadius: const BorderRadius.only(
                  topLeft: Radius.circular(20), topRight: Radius.circular(20)),
              child:
              Container(padding: const EdgeInsets.only(top: 13), child: view));
        });
  }

  static void showMyPicker<T>(BuildContext context,
      {required List<T> data,
        required String title,
        required String cancelText,
        required String confirmText,
        required int normalIndex,
        required _StringClickCallBack clickCallBack}) {
    showPickerDateTimeRoundBg(context,
        adapter: PickerDataAdapter(pickerdata: data, isArray: false),
        title: title,
        cancelText: cancelText,
        confirmText: confirmText,
        selecteds: [normalIndex],
        clickCallBack: (Pickering picker, List<int> selecteds) {
          clickCallBack(selecteds[0], data[selecteds[0]]!);
        });
  }

  /// 圆角背景
  static void showPickerDateTimeRoundBg(BuildContext context,
      {required PickerAdapter adapter,
        required String title,
        required String cancelText,
        required String confirmText,
        required List<int> selecteds,
        required PickerConfirmCallback clickCallBack}) {
    var picker = Pickering(
        adapter: adapter,
        backgroundColor: CommonColors.getColorFCFFFA,
        headerColor: CommonColors.getColorFCFFFA,
        diameterRatio: 5,
        //上下距离
        squeeze: 1,

        ///头背景
        headerDecoration: BoxDecoration(
            border: Border(
                bottom: BorderSide(
                    color: CommonColors.getColorFCFFFA, width: 0.5))),
        containerColor: CommonColors.getColorFCFFFA,

        ///选中框样式
        // selectionOverlay: Container(height: 40, color: const Color(0xB2E5E5E5)),
        title: ContentTextBlod(
            title, 16.0, CommonColors.getColor1A1A1A, FontWeight.w500),
        // cancel: ContentText(title, 16.0, CommonColors.getColor1A1A1A),
        cancelText: cancelText,
        cancelTextStyle:
        TextStyle(color: CommonColors.getColor1A1A1A, fontSize: 16.0),
        confirmText: confirmText,
        confirmTextStyle:
        TextStyle(color: CommonColors.getColor1A1A1A, fontSize: 16.0),
        selecteds: selecteds,
        textAlign: TextAlign.right,
        itemExtent: 40,
        height: MediaQuery.of(context).size.height / 3,
        selectedTextStyle: TextStyle(
            color: CommonColors.getColor1A1A1A, fontWeight: FontWeight.w500),
        textStyle: TextStyle(color: CommonColors.getColor1A1A1A, fontSize: 18),
        onConfirm: clickCallBack);

    picker.showModal(context, backgroundColor: Colors.transparent,
        builder: (context, view) {
          return Material(
              color: CommonColors.getColorFCFFFA,
              borderRadius: const BorderRadius.only(
                  topLeft: Radius.circular(20), topRight: Radius.circular(20)),
              child:
              Container(padding: const EdgeInsets.only(top: 4), child: view));
        });
  }
}

pickingDialog
这个文件是源码复制出来的,然后在这里面增加一个确定按钮,同时保证回调

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as Dialog;
import 'dart:async';

import 'package:flutter_picker/PickerLocalizations.dart';
import 'package:social_im/pages/widget/splitLine.dart';

import '../../generated/l10n.dart';

//选择器,将确定按钮放到底部
const bool __printDebug = true;

/// Picker selected callback.
typedef PickerSelectedCallback = void Function(
    Pickering picker, int index, List<int> selected);

/// Picker confirm callback.
typedef PickerConfirmCallback = void Function(
    Pickering picker, List<int> selected);

/// Picker confirm before callback.
typedef PickerConfirmBeforeCallback = Future<bool> Function(
    Pickering picker, List<int> selected);

/// Picker value format callback.
typedef PickerValueFormat<T> = String Function(T value);

/// Picker widget builder
typedef PickerWidgetBuilder = Widget Function(
    BuildContext context, Widget pickerWidget);

/// Picker build item, If 'null' is returned, the default build is used
typedef PickerItemBuilder = Widget? Function(BuildContext context, String? text,
    Widget? child, bool selected, int col, int index);

/// Picker
class Pickering {
  static const double DefaultTextSize = 18.0;

  /// Index of currently selected items
  late List<int> selecteds;

  /// Picker adapter, Used to provide data and generate widgets
  late PickerAdapter adapter;

  /// insert separator before picker columns
  final List<PickerDelimiter>? delimiter;

  final VoidCallback? onCancel;
  final PickerSelectedCallback? onSelect;
  final PickerConfirmCallback? onConfirm;
  final PickerConfirmBeforeCallback? onConfirmBefore;

  /// When the previous level selection changes, scroll the child to the first item.
  final changeToFirst;

  /// Specify flex for each column
  final List<int>? columnFlex;

  final Widget? title;
  final Widget? cancel;
  final Widget? confirm;
  final String? cancelText;
  final String? confirmText;

  final double height;

  /// Height of list item
  final double itemExtent;

  final TextStyle? textStyle,
      cancelTextStyle,
      confirmTextStyle,
      selectedTextStyle;
  final TextAlign textAlign;
  final IconThemeData? selectedIconTheme;

  /// Text scaling factor
  final double? textScaleFactor;

  final EdgeInsetsGeometry? columnPadding;
  final Color? backgroundColor, headerColor, containerColor;

  /// Hide head
  final bool hideHeader;

  /// Show pickers in reversed order
  final bool reversedOrder;

  /// Generate a custom header, [hideHeader] = true
  final WidgetBuilder? builderHeader;

  /// Generate a custom item widget, If 'null' is returned, the default builder is used
  final PickerItemBuilder? onBuilderItem;

  /// List item loop
  final bool looping;

  /// Delay generation for smoother animation, This is the number of milliseconds to wait. It is recommended to > = 200
  final int smooth;

  final Widget? footer;

  /// A widget overlaid on the picker to highlight the currently selected entry.
  final Widget selectionOverlay;

  final Decoration? headerDecoration;

  final double magnification;
  final double diameterRatio;
  final double squeeze;

  Widget? _widget;
  PickerWidgetState? _state;

  Pickering(
      {required this.adapter,
      this.delimiter,
      List<int>? selecteds,
      this.height = 150.0,
      this.itemExtent = 28.0,
      this.columnPadding,
      this.textStyle,
      this.cancelTextStyle,
      this.confirmTextStyle,
      this.selectedTextStyle,
      this.selectedIconTheme,
      this.textAlign = TextAlign.start,
      this.textScaleFactor,
      this.title,
      this.cancel,
      this.confirm,
      this.cancelText,
      this.confirmText,
      this.backgroundColor = Colors.white,
      this.containerColor,
      this.headerColor,
      this.builderHeader,
      this.changeToFirst = false,
      this.hideHeader = false,
      this.looping = false,
      this.reversedOrder = false,
      this.headerDecoration,
      this.columnFlex,
      this.footer,
      this.smooth = 0,
      this.magnification = 1.0,
      this.diameterRatio = 1.1,
      this.squeeze = 1.45,
      this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
      this.onBuilderItem,
      this.onCancel,
      this.onSelect,
      this.onConfirmBefore,
      this.onConfirm}) {
    this.selecteds = selecteds == null ? <int>[] : selecteds;
  }

  Widget? get widget => _widget;

  PickerWidgetState? get state => _state;
  int _maxLevel = 1;

  /// 生成picker控件
  ///
  /// Build picker control
  Widget makePicker([ThemeData? themeData, bool isModal = false, Key? key]) {
    _maxLevel = adapter.maxLevel;
    adapter.picker = this;
    adapter.initSelects();
    _widget = PickerWidget(
      key: key ?? ValueKey(this),
      child: _PickerWidget(
          picker: this,
          themeData: themeData,
          isModal: isModal,
          callBack: onConfirm,
          callBackContext: selecteds),
      data: this,
    );
    return _widget!;
  }

  /// show picker bottom sheet
  void show(
    ScaffoldState state, {
    ThemeData? themeData,
    Color? backgroundColor,
    PickerWidgetBuilder? builder,
  }) {
    state.showBottomSheet((BuildContext context) {
      final picker = makePicker(themeData);
      return builder == null ? picker : builder(context, picker);
    }, backgroundColor: backgroundColor);
  }

  /// show picker bottom sheet
  void showBottomSheet(
    BuildContext context, {
    ThemeData? themeData,
    Color? backgroundColor,
    PickerWidgetBuilder? builder,
  }) {
    Scaffold.of(context).showBottomSheet((BuildContext context) {
      final picker = makePicker(themeData);
      return builder == null ? picker : builder(context, picker);
    }, backgroundColor: backgroundColor);
  }

  /// Display modal picker
  Future<T?> showModal<T>(BuildContext context,
      {ThemeData? themeData,
      bool isScrollControlled = false,
      bool useRootNavigator = false,
      Color? backgroundColor,
      PickerWidgetBuilder? builder}) async {
    return await showModalBottomSheet<T>(
        context: context,
        //state.context,
        isScrollControlled: isScrollControlled,
        useRootNavigator: useRootNavigator,
        backgroundColor: backgroundColor,
        builder: (BuildContext context) {
          final picker = makePicker(themeData, true);
          return builder == null ? picker : builder(context, picker);
        });
  }

  /// show dialog picker
  Future<List<int>?> showDialog(BuildContext context,
      {bool barrierDismissible = true,
      Color? backgroundColor,
      PickerWidgetBuilder? builder,
      Key? key}) {
    return Dialog.showDialog<List<int>>(
        context: context,
        barrierDismissible: barrierDismissible,
        builder: (BuildContext context) {
          final actions = <Widget>[];
          final theme = Theme.of(context);
          final _cancel = PickerWidgetState._buildButton(
              context, cancelText, cancel, cancelTextStyle, true, theme, () {
            Navigator.pop<List<int>>(context, null);
            if (onCancel != null) {
              onCancel!();
            }
          });
          if (_cancel != null) {
            actions.add(_cancel);
          }
          final _confirm = PickerWidgetState._buildButton(
              context, confirmText, confirm, confirmTextStyle, false, theme,
              () async {
            if (onConfirmBefore != null &&
                !(await onConfirmBefore!(this, selecteds))) {
              return; // Cancel;
            }
            Navigator.pop<List<int>>(context, selecteds);
            if (onConfirm != null) {
              onConfirm!(this, selecteds);
            }
          });
          if (_confirm != null) {
            actions.add(_confirm);
          }
          return AlertDialog(
            key: key ?? Key('picker-dialog'),
            title: title,
            backgroundColor: backgroundColor,
            actions: actions,
            content: builder == null
                ? makePicker(theme)
                : builder(context, makePicker(theme)),
          );
        });
  }

  /// 获取当前选择的值
  /// Get the value of the current selection
  List getSelectedValues() {
    return adapter.getSelectedValues();
  }

  /// 取消
  void doCancel(BuildContext context) {
    Navigator.of(context).pop<List<int>>(null);
    if (onCancel != null) onCancel!();
    _widget = null;
  }

  /// 确定
  void doConfirm(BuildContext context) async {
    if (onConfirmBefore != null && !(await onConfirmBefore!(this, selecteds))) {
      return; // Cancel;
    }
    Navigator.of(context).pop<List<int>>(selecteds);
    if (onConfirm != null) onConfirm!(this, selecteds);
    _widget = null;
  }

  /// 弹制更新指定列的内容
  /// 当 onSelect 事件中,修改了当前列前面的列的内容时,可以调用此方法来更新显示
  void updateColumn(int index, [bool all = false]) {
    if (all) {
      _state?.update();
      return;
    }
    if (_state?._keys[index] != null) {
      adapter.setColumn(index - 1);
      _state?._keys[index]!(() => null);
    }
  }

  static ButtonStyle _getButtonStyle(ButtonThemeData? theme,
          [isCancelButton = false]) =>
      TextButton.styleFrom(
          minimumSize: Size(theme?.minWidth ?? 0.0, 42),
          textStyle: TextStyle(
            fontSize: Pickering.DefaultTextSize,
            color: isCancelButton ? null : theme?.colorScheme?.secondary,
          ),
          padding: theme?.padding);
}

/// 分隔符
class PickerDelimiter {
  final Widget? child;
  final int column;

  PickerDelimiter({required this.child, this.column = 1});
}

/// picker data list item
class PickerItem<T> {
  /// 显示内容
  final Widget? text;

  /// 数据值
  final T? value;

  /// 子项
  final List<PickerItem<T>>? children;

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

class PickerWidget<T> extends InheritedWidget {
  final Pickering data;

  const PickerWidget({Key? key, required this.data, required Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(covariant PickerWidget oldWidget) =>
      oldWidget.data != data;

  static PickerWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<PickerWidget>()
        as PickerWidget;
  }
}

class _PickerWidget<T> extends StatefulWidget {
  final Pickering picker;
  final ThemeData? themeData;
  final bool isModal;
  final PickerConfirmCallback? callBack;
  final List<int> callBackContext;

  _PickerWidget(
      {Key? key,
      required this.picker,
      this.themeData,
      required this.isModal,
      this.callBack,
      required this.callBackContext})
      : super(key: key);

  @override
  PickerWidgetState createState() => PickerWidgetState<T>(
      picker: this.picker,
      themeData: this.themeData,
      callBack: this.callBack,
      callBackContext: callBackContext);
}

class PickerWidgetState<T> extends State<_PickerWidget> {
  final Pickering picker;
  final ThemeData? themeData;
  final PickerConfirmCallback? callBack;
  final List<int> callBackContext;

  PickerWidgetState(
      {required this.picker,
      this.themeData,
      this.callBack,
      required this.callBackContext});

  ThemeData? theme;
  final List<FixedExtentScrollController> scrollController = [];
  final List<StateSetter?> _keys = [];

  @override
  void initState() {
    super.initState();
    picker._state = this;
    picker.adapter.doShow();

    if (scrollController.length == 0) {
      for (int i = 0; i < picker._maxLevel; i++) {
        scrollController
            .add(FixedExtentScrollController(initialItem: picker.selecteds[i]));
        _keys.add(null);
      }
    }
  }

  void update() {
    setState(() {});
  }

  // var ref = 0;
  @override
  Widget build(BuildContext context) {
    // print("picker build ${ref++}");
    theme = themeData ?? Theme.of(context);

    if (_wait && picker.smooth > 0) {
      Future.delayed(Duration(milliseconds: picker.smooth), () {
        if (!_wait) return;
        setState(() {
          _wait = false;
        });
      });
    } else
      _wait = false;

    final _body = <Widget>[];
    if (!picker.hideHeader) {
      if (picker.builderHeader != null) {
        _body.add(picker.headerDecoration == null
            ? picker.builderHeader!(context)
            : DecoratedBox(
                child: picker.builderHeader!(context),
                decoration: picker.headerDecoration!));
      } else {
        _body.add(DecoratedBox(
          child: Row(
            children: _buildHeaderViews(context),
          ),
          decoration: picker.headerDecoration ??
              BoxDecoration(
                border: Border(
                  top: BorderSide(color: theme!.dividerColor, width: 0.5),
                  bottom: BorderSide(color: theme!.dividerColor, width: 0.5),
                ),
                color: picker.headerColor == null
                    ? (theme!.bottomAppBarColor)
                    : picker.headerColor,
              ),
        ));
      }
    }

    _body.add(_wait
        ? Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: _buildViews(),
          )
        : AnimatedSwitcher(
            duration: Duration(milliseconds: 300),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: _buildViews(),
            ),
          ));

    _body.add(_wait
        ? GestureDetector(
            onTap: () {
              if (widget.callBack != null)
                widget.callBack!(widget.picker, widget.callBackContext);
            },
            child: Container(
              child: Text(
                S.current.btn_ok,
                style: const TextStyle(color: Color(0xFF664C00), fontSize: 18,fontWeight: FontWeight.w500),
              ),
            ))
        : GestureDetector(
            onTap: () {
              if (widget.callBack != null)
                widget.callBack!(widget.picker, widget.callBackContext);
            },
            child: Container(
              margin: EdgeInsets.only(left: 15, right: 15, bottom: 8),
              padding: EdgeInsets.only(top: 13, bottom: 13),
              decoration:
                  BoxDecorationUtil().setFillBoxDecoration(Color(0xFFFFC740), 10.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    S.current.btn_ok,
                    style: const TextStyle(color: Color(0xFF664C00), fontSize: 17,fontWeight: FontWeight.w500),
                  )
                ],
              ),
            )));

    if (picker.footer != null) _body.add(picker.footer!);
    Widget v = Column(
      mainAxisSize: MainAxisSize.min,
      children: _body,
    );
    if (widget.isModal) {
      return GestureDetector(
        onTap: () {},
        child: v,
      );
    }
    return v;
  }

  List<Widget>? _headerItems;

  List<Widget> _buildHeaderViews(BuildContext context) {
    if (_headerItems != null) {
      return _headerItems!;
    }
    if (theme == null) theme = Theme.of(context);
    List<Widget> items = [];

    final _cancel = _buildButton(context, picker.cancelText, picker.cancel,
        picker.cancelTextStyle, true, theme, () => picker.doCancel(context));
    if (_cancel != null) {
      items.add(_cancel);
    }

    items.add(Expanded(
      child: picker.title == null
          ? SizedBox()
          : DefaultTextStyle(
              style: theme!.textTheme.headline6?.copyWith(
                    fontSize: Pickering.DefaultTextSize,
                  ) ??
                  TextStyle(fontSize: Pickering.DefaultTextSize),
              textAlign: TextAlign.center,
              overflow: TextOverflow.ellipsis,
              child: picker.title!),
    ));

    final _confirm = _buildButton(context, picker.confirmText, picker.confirm,
        picker.confirmTextStyle, false, theme, () => picker.doConfirm(context));
    if (_confirm != null) {
      items.add(_confirm);
    }

    _headerItems = items;
    return items;
  }

  static Widget? _buildButton(
      BuildContext context,
      String? text,
      Widget? widget,
      TextStyle? textStyle,
      bool isCancel,
      ThemeData? theme,
      VoidCallback? onPressed) {
    if (widget == null) {
      String? _txt = text ??
          (isCancel
              ? PickerLocalizations.of(context).cancelText
              : PickerLocalizations.of(context).confirmText);
      if (_txt == null || _txt.isEmpty) {
        return null;
      }
      return TextButton(
          style: Pickering._getButtonStyle(ButtonTheme.of(context), isCancel),
          onPressed: onPressed,
          child: Text(_txt,
              overflow: TextOverflow.ellipsis,
              textScaleFactor: MediaQuery.of(context).textScaleFactor,
              style: textStyle));
    } else {
      return textStyle == null
          ? widget
          : DefaultTextStyle(style: textStyle, child: widget);
    }
  }

  bool _changing = false;
  bool _wait = true;
  final Map<int, int> lastData = {};

  List<Widget> _buildViews() {
    if (__printDebug) print("_buildViews");
    if (theme == null) theme = Theme.of(context);
    for (int j = 0; j < _keys.length; j++) _keys[j] = null;

    List<Widget> items = [];
    PickerAdapter? adapter = picker.adapter;
    adapter.setColumn(-1);

    if (adapter.length > 0) {
      var _decoration = BoxDecoration(
        color: picker.containerColor == null
            ? theme!.dialogBackgroundColor
            : picker.containerColor,
      );

      for (int i = 0; i < picker._maxLevel; i++) {
        Widget view = Expanded(
          flex: adapter.getColumnFlex(i),
          child: Container(
            padding: picker.columnPadding,
            height: picker.height,
            decoration: _decoration,
            child: _wait
                ? null
                : StatefulBuilder(
                    builder: (context, state) {
                      _keys[i] = state;
                      adapter.setColumn(i - 1);
                      if (__printDebug) print("builder. col: $i");

                      // 上一次是空列表
                      final _lastIsEmpty = scrollController[i].hasClients &&
                          !scrollController[i].position.hasContentDimensions;

                      final _length = adapter.length;
                      final _view = _buildCupertinoPicker(context, i, _length,
                          adapter, _lastIsEmpty ? ValueKey(_length) : null);

                      if (_lastIsEmpty ||
                          (!picker.changeToFirst &&
                              picker.selecteds[i] >= _length)) {
                        Timer(Duration(milliseconds: 100), () {
                          if (!this.mounted) return;
                          if (__printDebug) print("timer last");
                          var _len = adapter.length;
                          var _index = (_len < _length ? _len : _length) - 1;
                          if (scrollController[i]
                              .position
                              .hasContentDimensions) {
                            scrollController[i].jumpToItem(_index);
                          } else {
                            scrollController[i] = FixedExtentScrollController(
                                initialItem: _index);
                            if (_keys[i] != null) {
                              _keys[i]!(() => null);
                            }
                          }
                        });
                      }

                      return _view;
                    },
                  ),
          ),
        );
        items.add(view);
      }
    }

    if (picker.delimiter != null && !_wait) {
      for (int i = 0; i < picker.delimiter!.length; i++) {
        var o = picker.delimiter![i];
        if (o.child == null) continue;
        var item = SizedBox(child: o.child, height: picker.height);
        if (o.column < 0)
          items.insert(0, item);
        else if (o.column >= items.length)
          items.add(item);
        else
          items.insert(o.column, item);
      }
    }

    if (picker.reversedOrder) return items.reversed.toList();

    return items;
  }

  Widget _buildCupertinoPicker(BuildContext context, int i, int _length,
      PickerAdapter adapter, Key? key) {
    return CupertinoPicker.builder(
      key: key,
      backgroundColor: picker.backgroundColor,
      scrollController: scrollController[i],
      itemExtent: picker.itemExtent,
      // looping: picker.looping,
      magnification: picker.magnification,
      diameterRatio: picker.diameterRatio,
      squeeze: picker.squeeze,
      selectionOverlay: picker.selectionOverlay,
      childCount: picker.looping ? null : _length,
      itemBuilder: (context, index) {
        adapter.setColumn(i - 1);
        return adapter.buildItem(context, index % _length);
      },
      onSelectedItemChanged: (int _index) {
        if (_length <= 0) return;
        var index = _index % _length;
        if (__printDebug) print("onSelectedItemChanged. col: $i, row: $index");
        picker.selecteds[i] = index;
        updateScrollController(i);
        adapter.doSelect(i, index);
        if (picker.changeToFirst) {
          for (int j = i + 1; j < picker.selecteds.length; j++) {
            picker.selecteds[j] = 0;
            scrollController[j].jumpTo(0.0);
          }
        }
        if (picker.onSelect != null)
          picker.onSelect!(picker, i, picker.selecteds);

        if (adapter.needUpdatePrev(i)) {
          for (int j = 0; j < picker.selecteds.length; j++) {
            if (j != i && _keys[j] != null) {
              adapter.setColumn(j - 1);
              _keys[j]!(() => null);
            }
          }
          // setState(() {});
        } else {
          if (_keys[i] != null) _keys[i]!(() => null);
          if (adapter.isLinkage) {
            for (int j = i + 1; j < picker.selecteds.length; j++) {
              if (j == i) continue;
              adapter.setColumn(j - 1);
              _keys[j]?.call(() => null);
            }
          }
        }
      },
    );
  }

  void updateScrollController(int col) {
    if (_changing || picker.adapter.isLinkage == false) return;
    _changing = true;
    for (int j = 0; j < picker.selecteds.length; j++) {
      if (j != col) {
        if (scrollController[j].hasClients &&
            scrollController[j].position.hasContentDimensions) {
          scrollController[j].position.notifyListeners();
        }
      }
    }
    _changing = false;
  }

  @override
  void debugFillProperties(properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<bool>('_changing', _changing));
  }
}

/// 选择器数据适配器
abstract class PickerAdapter<T> {
  Pickering? picker;

  int getLength();

  int getMaxLevel();

  void setColumn(int index);

  void initSelects();

  Widget buildItem(BuildContext context, int index);

  /// 是否需要更新前面的列
  /// Need to update previous columns
  bool needUpdatePrev(int curIndex) {
    return false;
  }

  Widget makeText(Widget? child, String? text, bool isSel) {
    return Center(
        child: DefaultTextStyle(
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            textAlign: picker!.textAlign,
            style: picker!.textStyle ??
                TextStyle(
                    color: Colors.black87,
                    fontFamily: picker?.state?.context != null
                        ? Theme.of(picker!.state!.context)
                            .textTheme
                            .headline6!
                            .fontFamily
                        : "",
                    fontSize: Pickering.DefaultTextSize),
            child: child != null
                ? (isSel && picker!.selectedIconTheme != null
                    ? IconTheme(
                        data: picker!.selectedIconTheme!,
                        child: child,
                      )
                    : child)
                : Text(text ?? "",
                    textScaleFactor: picker!.textScaleFactor,
                    style: (isSel ? picker!.selectedTextStyle : null))));
  }

  Widget makeTextEx(
      Widget? child, String text, Widget? postfix, Widget? suffix, bool isSel) {
    List<Widget> items = [];
    if (postfix != null) items.add(postfix);
    items.add(
        child ?? Text(text, style: (isSel ? picker!.selectedTextStyle : null)));
    if (suffix != null) items.add(suffix);

    Color? _txtColor = Colors.black87;
    double? _txtSize = Pickering.DefaultTextSize;
    if (isSel && picker!.selectedTextStyle != null) {
      if (picker!.selectedTextStyle!.color != null)
        _txtColor = picker!.selectedTextStyle!.color;
      if (picker!.selectedTextStyle!.fontSize != null)
        _txtSize = picker!.selectedTextStyle!.fontSize;
    }

    return new Center(
        //alignment: Alignment.center,
        child: DefaultTextStyle(
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            textAlign: picker!.textAlign,
            style: picker!.textStyle ??
                TextStyle(color: _txtColor, fontSize: _txtSize),
            child: Wrap(
              children: items,
            )));
  }

  String getText() {
    return getSelectedValues().toString();
  }

  List<T> getSelectedValues() {
    return [];
  }

  void doShow() {}

  void doSelect(int column, int index) {}

  int getColumnFlex(int column) {
    if (picker!.columnFlex != null && column < picker!.columnFlex!.length)
      return picker!.columnFlex![column];
    return 1;
  }

  int get maxLevel => getMaxLevel();

  /// Content length of current column
  int get length => getLength();

  String get text => getText();

  // 是否联动,即后面的列受前面列数据影响
  bool get isLinkage => getIsLinkage();

  @override
  String toString() {
    return getText();
  }

  bool getIsLinkage() {
    return true;
  }

  /// 通知适配器数据改变
  void notifyDataChanged() {
    if (picker?.state != null) {
      picker!.adapter.doShow();
      picker!.adapter.initSelects();
      for (int j = 0; j < picker!.selecteds.length; j++) {
        picker!.state!.scrollController[j].jumpToItem(picker!.selecteds[j]);
      }
    }
  }
}

/// 数据适配器
class PickerDataAdapter<T> extends PickerAdapter<T> {
  late List<PickerItem<T>> data;
  List<PickerItem<dynamic>>? _datas;
  int _maxLevel = -1;
  int _col = 0;
  final bool isArray;

  PickerDataAdapter(
      {List? pickerdata, List<PickerItem<T>>? data, this.isArray = false}) {
    this.data = data ?? <PickerItem<T>>[];
    _parseData(pickerdata);
  }

  @override
  bool getIsLinkage() {
    return !isArray;
  }

  void _parseData(List? pickerData) {
    if (pickerData != null && pickerData.length > 0 && (data.length == 0)) {
      if (isArray) {
        _parseArrayPickerDataItem(pickerData, data);
      } else {
        _parsePickerDataItem(pickerData, data);
      }
    }
  }

  _parseArrayPickerDataItem(List? pickerData, List<PickerItem> data) {
    if (pickerData == null) return;
    var len = pickerData.length;
    for (int i = 0; i < len; i++) {
      var v = pickerData[i];
      if (!(v is List)) continue;
      List lv = v;
      if (lv.length == 0) continue;

      PickerItem item = PickerItem<T>(children: <PickerItem<T>>[]);
      data.add(item);

      for (int j = 0; j < lv.length; j++) {
        var o = lv[j];
        if (o is T) {
          item.children!.add(PickerItem<T>(value: o));
        } else if (T == String) {
          String _v = o.toString();
          item.children!.add(PickerItem<T>(value: _v as T));
        }
      }
    }
    if (__printDebug) print("data.length: ${data.length}");
  }

  _parsePickerDataItem(List? pickerData, List<PickerItem> data) {
    if (pickerData == null) return;
    var len = pickerData.length;
    for (int i = 0; i < len; i++) {
      var item = pickerData[i];
      if (item is T) {
        data.add(new PickerItem<T>(value: item));
      } else if (item is Map) {
        final Map map = item;
        if (map.length == 0) continue;

        List<T> _mapList = map.keys.toList().cast();
        for (int j = 0; j < _mapList.length; j++) {
          var _o = map[_mapList[j]];
          if (_o is List && _o.length > 0) {
            List<PickerItem<T>> _children = <PickerItem<T>>[];
            //print('add: ${data.runtimeType.toString()}');
            data.add(PickerItem<T>(value: _mapList[j], children: _children));
            _parsePickerDataItem(_o, _children);
          }
        }
      } else if (T == String && !(item is List)) {
        String _v = item.toString();
        //print('add: $_v');
        data.add(PickerItem<T>(value: _v as T));
      }
    }
  }

  void setColumn(int index) {
    if (_datas != null && _col == index + 1) return;
    _col = index + 1;
    if (isArray) {
      if (__printDebug) print("index: $index");
      if (_col < data.length)
        _datas = data[_col].children;
      else
        _datas = null;
      return;
    }
    if (index < 0) {
      _datas = data;
    } else {
      _datas = data;
      // 列数过多会有性能问题
      for (int i = 0; i <= index; i++) {
        var j = picker!.selecteds[i];
        if (_datas != null && _datas!.length > j)
          _datas = _datas![j].children;
        else {
          _datas = null;
          break;
        }
      }
    }
  }

  @override
  int getLength() => _datas?.length ?? 0;

  @override
  getMaxLevel() {
    if (_maxLevel == -1) _checkPickerDataLevel(data, 1);
    return _maxLevel;
  }

  @override
  Widget buildItem(BuildContext context, int index) {
    final PickerItem item = _datas![index];
    final isSel = index == picker!.selecteds[_col];
    if (picker!.onBuilderItem != null) {
      final _v = picker!.onBuilderItem!(
          context, item.value.toString(), item.text, isSel, _col, index);
      if (_v != null) return _v;
    }
    if (item.text != null) {
      return isSel && picker!.selectedTextStyle != null
          ? DefaultTextStyle(
              style: picker!.selectedTextStyle!,
              child: picker!.selectedIconTheme != null
                  ? IconTheme(
                      data: picker!.selectedIconTheme!,
                      child: item.text!,
                    )
                  : item.text!)
          : item.text!;
    }
    return makeText(
        item.text, item.text != null ? null : item.value.toString(), isSel);
  }

  @override
  void initSelects() {
    // ignore: unnecessary_null_comparison
    if (picker!.selecteds == null) picker!.selecteds = <int>[];
    if (picker!.selecteds.length == 0) {
      for (int i = 0; i < _maxLevel; i++) picker!.selecteds.add(0);
    }
  }

  @override
  List<T> getSelectedValues() {
    List<T> _items = [];
    var _sLen = picker!.selecteds.length;
    if (isArray) {
      for (int i = 0; i < _sLen; i++) {
        int j = picker!.selecteds[i];
        if (j < 0 || data[i].children == null || j >= data[i].children!.length)
          break;
        _items.add(data[i].children![j].value!);
      }
    } else {
      List<PickerItem<dynamic>>? datas = data;
      for (int i = 0; i < _sLen; i++) {
        int j = picker!.selecteds[i];
        if (j < 0 || j >= datas!.length) break;
        _items.add(datas[j].value);
        datas = datas[j].children;
        if (datas == null || datas.length == 0) break;
      }
    }
    return _items;
  }

  _checkPickerDataLevel(List<PickerItem>? data, int level) {
    if (data == null) return;
    if (isArray) {
      _maxLevel = data.length;
      return;
    }
    for (int i = 0; i < data.length; i++) {
      if (data[i].children != null && data[i].children!.length > 0)
        _checkPickerDataLevel(data[i].children, level + 1);
    }
    if (_maxLevel < level) _maxLevel = level;
  }
}

class NumberPickerColumn {
  final List<int>? items;
  final int begin;
  final int end;
  final int? initValue;
  final int columnFlex;
  final int jump;
  final Widget? postfix, suffix;
  final PickerValueFormat<int>? onFormatValue;

  const NumberPickerColumn({
    this.begin = 0,
    this.end = 9,
    this.items,
    this.initValue,
    this.jump = 1,
    this.columnFlex = 1,
    this.postfix,
    this.suffix,
    this.onFormatValue,
  });

  int indexOf(int? value) {
    if (value == null) return -1;
    if (items != null) return items!.indexOf(value);
    if (value < begin || value > end) return -1;
    return (value - begin) ~/ (this.jump == 0 ? 1 : this.jump);
  }

  int valueOf(int index) {
    if (items != null) {
      return items![index];
    }
    return begin + index * (this.jump == 0 ? 1 : this.jump);
  }

  String getValueText(int index) {
    return onFormatValue == null
        ? "${valueOf(index)}"
        : onFormatValue!(valueOf(index));
  }

  int count() {
    var v = (end - begin) ~/ (this.jump == 0 ? 1 : this.jump) + 1;
    if (v < 1) return 0;
    return v;
  }
}

class NumberPickerAdapter extends PickerAdapter<int> {
  NumberPickerAdapter({required this.data});

  final List<NumberPickerColumn> data;
  NumberPickerColumn? cur;
  int _col = 0;

  @override
  int getLength() {
    if (cur == null) return 0;
    if (cur!.items != null) return cur!.items!.length;
    return cur!.count();
  }

  @override
  int getMaxLevel() => data.length;

  @override
  bool getIsLinkage() {
    return false;
  }

  @override
  void setColumn(int index) {
    if (index != -1 && _col == index + 1) return;
    _col = index + 1;
    if (_col >= data.length) {
      cur = null;
    } else {
      cur = data[_col];
    }
  }

  @override
  void initSelects() {
    int _maxLevel = getMaxLevel();
    // ignore: unnecessary_null_comparison
    if (picker!.selecteds == null) picker!.selecteds = <int>[];
    if (picker!.selecteds.length == 0) {
      for (int i = 0; i < _maxLevel; i++) {
        int v = data[i].indexOf(data[i].initValue);
        if (v < 0) v = 0;
        picker!.selecteds.add(v);
      }
    }
  }

  @override
  Widget buildItem(BuildContext context, int index) {
    final txt = cur!.getValueText(index);
    final isSel = index == picker!.selecteds[_col];
    if (picker!.onBuilderItem != null) {
      final _v = picker!.onBuilderItem!(context, txt, null, isSel, _col, index);
      if (_v != null) return _v;
    }
    if (cur!.postfix == null && cur!.suffix == null)
      return makeText(null, txt, isSel);
    else
      return makeTextEx(null, txt, cur!.postfix, cur!.suffix, isSel);
  }

  @override
  int getColumnFlex(int column) {
    return data[column].columnFlex;
  }

  @override
  List<int> getSelectedValues() {
    List<int> _items = [];
    for (int i = 0; i < picker!.selecteds.length; i++) {
      int j = picker!.selecteds[i];
      int v = data[i].valueOf(j);
      _items.add(v);
    }
    return _items;
  }
}

/// Picker DateTime Adapter Type
class PickerDateTimeType {
  static const int kMDY = 0; // m, d, y
  static const int kHM = 1; // hh, mm
  static const int kHMS = 2; // hh, mm, ss
  static const int kHM_AP = 3; // hh, mm, ap(AM/PM)
  static const int kMDYHM = 4; // m, d, y, hh, mm
  static const int kMDYHM_AP = 5; // m, d, y, hh, mm, AM/PM
  static const int kMDYHMS = 6; // m, d, y, hh, mm, ss

  static const int kYMD = 7; // y, m, d
  static const int kYMDHM = 8; // y, m, d, hh, mm
  static const int kYMDHMS = 9; // y, m, d, hh, mm, ss
  static const int kYMD_AP_HM = 10; // y, m, d, ap, hh, mm

  static const int kYM = 11; // y, m
  static const int kDMY = 12; // d, m, y
  static const int kY = 13; // y
}

class DateTimePickerAdapter extends PickerAdapter<DateTime> {
  /// display type, ref: [columnType]
  final int type;

  /// Whether to display the month in numerical form.If true, months is not used.
  final bool isNumberMonth;

  /// custom months strings
  final List<String>? months;

  /// Custom AM, PM strings
  final List<String>? strAMPM;

  /// year begin...end.
  final int? yearBegin, yearEnd;

  /// hour min ... max, min >= 0, max <= 23, max > min
  final int? minHour, maxHour;

  /// minimum datetime
  final DateTime? minValue, maxValue;

  /// jump minutes, user could select time in intervals of 30min, 5mins, etc....
  final int? minuteInterval;

  /// Year, month, day suffix
  final String? yearSuffix,
      monthSuffix,
      daySuffix,
      hourSuffix,
      minuteSuffix,
      secondSuffix;

  /// use two-digit year, 2019, displayed as 19
  final bool twoDigitYear;

  /// year 0, month 1, day 2, hour 3, minute 4, sec 5, am/pm 6, hour-ap: 7
  final List<int>? customColumnType;

  static const List<String> MonthsList_EN = const [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
  ];

  static const List<String> MonthsList_EN_L = const [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
  ];

  DateTimePickerAdapter({
    Pickering? picker,
    this.type = 0,
    this.isNumberMonth = false,
    this.months = MonthsList_EN,
    this.strAMPM,
    this.yearBegin = 1900,
    this.yearEnd = 2100,
    this.value,
    this.minValue,
    this.maxValue,
    this.minHour,
    this.maxHour,
    this.secondSuffix,
    this.minuteSuffix,
    this.hourSuffix,
    this.yearSuffix,
    this.monthSuffix,
    this.daySuffix,
    this.minuteInterval,
    this.customColumnType,
    this.twoDigitYear = false,
  }) : assert(minuteInterval == null ||
            (minuteInterval >= 1 &&
                minuteInterval <= 30 &&
                (60 % minuteInterval == 0))) {
    super.picker = picker;
    _yearBegin = yearBegin ?? 0;
    if (minValue != null && minValue!.year > _yearBegin) {
      _yearBegin = minValue!.year;
    }
    // Judge whether the day is in front of the month
    // If in the front, set "needUpdatePrev" = true
    List<int> _columnType;
    if (customColumnType != null)
      _columnType = customColumnType!;
    else
      _columnType = columnType[type];
    var month = _columnType.indexWhere((element) => element == 1);
    var day = _columnType.indexWhere((element) => element == 2);
    _needUpdatePrev =
        day < month || day < _columnType.indexWhere((element) => element == 0);
    if (!_needUpdatePrev) {
      // check am/pm before hour-ap
      var ap = _columnType.indexWhere((element) => element == 6);
      if (ap > _columnType.indexWhere((element) => element == 7)) {
        _apBeforeHourAp = true;
        _needUpdatePrev = true;
      }
    }
    if (value == null) {
      value = DateTime.now();
    }
    _existSec = existSec();
    _verificationMinMaxValue();
  }

  bool _existSec = false;
  int _col = 0;
  int _colAP = -1;
  int _colHour = -1;
  int _colDay = -1;
  int _yearBegin = 0;
  bool _needUpdatePrev = false;
  bool _apBeforeHourAp = false;

  /// Currently selected value
  DateTime? value;

  // but it can improve the performance, so keep it.
  static const List<List<int>> lengths = const [
    [12, 31, 0],
    [24, 60],
    [24, 60, 60],
    [12, 60, 2],
    [12, 31, 0, 24, 60],
    [12, 31, 0, 12, 60, 2],
    [12, 31, 0, 24, 60, 60],
    [0, 12, 31],
    [0, 12, 31, 24, 60],
    [0, 12, 31, 24, 60, 60],
    [0, 12, 31, 2, 12, 60],
    [0, 12],
    [31, 12, 0],
    [0],
  ];

  static const Map<int, int> columnTypeLength = {
    0: 0,
    1: 12,
    2: 31,
    3: 24,
    4: 60,
    5: 60,
    6: 2,
    7: 12
  };

  /// year 0, month 1, day 2, hour 3, minute 4, sec 5, am/pm 6, hour-ap: 7
  static const List<List<int>> columnType = const [
    [1, 2, 0],
    [3, 4],
    [3, 4, 5],
    [7, 4, 6],
    [1, 2, 0, 3, 4],
    [1, 2, 0, 7, 4, 6],
    [1, 2, 0, 3, 4, 5],
    [0, 1, 2],
    [0, 1, 2, 3, 4],
    [0, 1, 2, 3, 4, 5],
    [0, 1, 2, 6, 7, 4],
    [0, 1],
    [2, 1, 0],
    [0],
  ];

  // static const List<int> leapYearMonths = const <int>[1, 3, 5, 7, 8, 10, 12];

  // 获取当前列的类型
  int getColumnType(int index) {
    if (customColumnType != null) return customColumnType![index];
    List<int> items = columnType[type];
    if (index >= items.length) return -1;
    return items[index];
  }

  // 判断是否存在秒
  bool existSec() {
    final _columns =
        customColumnType == null ? columnType[type] : customColumnType!;
    return _columns.indexOf(5) >= 0;
  }

  @override
  int getLength() {
    int v = (customColumnType == null
        ? lengths[type][_col]
        : columnTypeLength[customColumnType![_col]])!;
    if (v == 0) {
      int ye = yearEnd!;
      if (maxValue != null) ye = maxValue!.year;
      return ye - _yearBegin + 1;
    }
    if (v == 31) return _calcDateCount(value!.year, value!.month);
    int _type = getColumnType(_col);
    switch (_type) {
      case 3: // hour
        if ((minHour != null && minHour! >= 0) ||
            (maxHour != null && maxHour! <= 23))
          return (maxHour ?? 23) - (minHour ?? 0) + 1;
        break;
      case 4: // minute
        if (minuteInterval != null && minuteInterval! > 1)
          return v ~/ minuteInterval!;
        break;
      case 7: // hour am/pm
        if ((minHour != null && minHour! >= 0) ||
            (maxHour != null && maxHour! <= 23)) if (_colAP < 0) {
          // I don't know am or PM
          return 12;
        } else {
          var _min = 0;
          var _max = 0;
          if (picker!.selecteds[_colAP] == 0) {
            // am
            _min = minHour == null
                ? 1
                : minHour! >= 12
                    ? 12
                    : minHour! + 1;
            _max = maxHour == null
                ? 12
                : maxHour! >= 12
                    ? 12
                    : maxHour! + 1;
          } else {
            // pm
            _min = minHour == null
                ? 1
                : minHour! >= 12
                    ? 24 - minHour! - 12
                    : 1;
            _max = maxHour == null
                ? 12
                : maxHour! >= 12
                    ? maxHour! - 12
                    : 1;
          }
          return _max > _min ? _max - _min + 1 : _min - _max + 1;
        }
        break;
    }
    return v;
  }

  @override
  int getMaxLevel() {
    return customColumnType == null
        ? lengths[type].length
        : customColumnType!.length;
  }

  @override
  bool needUpdatePrev(int curIndex) {
    if (_needUpdatePrev) {
      if (value?.month == 2) {
        // Only February needs to be dealt with
        var _curType = getColumnType(curIndex);
        return _curType == 1 || _curType == 0;
      } else if (_apBeforeHourAp) {
        return getColumnType(curIndex) == 6;
      }
    }
    return false;
  }

  @override
  void setColumn(int index) {
    //print("setColumn index: $index");
    _col = index + 1;
    if (_col < 0) _col = 0;
  }

  @override
  void initSelects() {
    _colAP = _getAPColIndex();
    int _maxLevel = getMaxLevel();
    // ignore: unnecessary_null_comparison
    if (picker!.selecteds == null) picker!.selecteds = <int>[];
    if (picker!.selecteds.length == 0) {
      for (int i = 0; i < _maxLevel; i++) picker!.selecteds.add(0);
    }
  }

  @override
  Widget buildItem(BuildContext context, int index) {
    String _text = "";
    int colType = getColumnType(_col);
    switch (colType) {
      case 0:
        if (twoDigitYear) {
          _text = "${_yearBegin + index}";
          var _l = _text.length;
          _text =
              "${_text.substring(_l - (_l - 2), _l)}${_checkStr(yearSuffix)}";
        } else
          _text = "${_yearBegin + index}${_checkStr(yearSuffix)}";
        break;
      case 1:
        if (isNumberMonth) {
          _text = "${index + 1}${_checkStr(monthSuffix)}";
        } else {
          if (months != null)
            _text = "${months![index]}";
          else {
            List _months =
                PickerLocalizations.of(context).months ?? MonthsList_EN;
            _text = "${_months[index]}";
          }
        }
        break;
      case 2:
        _text = "${index + 1}${_checkStr(daySuffix)}";
        break;
      case 3:
        _text = "${intToStr(index + (minHour ?? 0))}${_checkStr(hourSuffix)}";
        break;
      case 5:
        _text = "${intToStr(index)}${_checkStr(secondSuffix)}";
        break;
      case 4:
        if (minuteInterval == null || minuteInterval! < 2)
          _text = "${intToStr(index)}${_checkStr(minuteSuffix)}";
        else
          _text =
              "${intToStr(index * minuteInterval!)}${_checkStr(minuteSuffix)}";
        break;
      case 6:
        List? _ampm = strAMPM ?? PickerLocalizations.of(context).ampm;
        if (_ampm == null) _ampm = const ['AM', 'PM'];
        _text = "${_ampm[index]}";
        break;
      case 7:
        _text =
            "${intToStr(index + (minHour == null ? 0 : (picker!.selecteds[_colAP] == 0 ? minHour! : 0)) + 1)}";
        break;
    }

    final isSel = picker!.selecteds[_col] == index;
    if (picker!.onBuilderItem != null) {
      var _v = picker!.onBuilderItem!(context, _text, null, isSel, _col, index);
      if (_v != null) return _v;
    }
    return makeText(null, _text, isSel);
  }

  @override
  String getText() {
    return value.toString();
  }

  @override
  int getColumnFlex(int column) {
    if (picker!.columnFlex != null && column < picker!.columnFlex!.length)
      return picker!.columnFlex![column];
    if (getColumnType(column) == 0) return 3;
    return 2;
  }

  @override
  void doShow() {
    if (_yearBegin == 0) getLength();
    var _maxLevel = getMaxLevel();
    for (int i = 0; i < _maxLevel; i++) {
      int colType = getColumnType(i);
      switch (colType) {
        case 0:
          picker!.selecteds[i] = yearEnd != null && value!.year > yearEnd!
              ? yearEnd! - _yearBegin
              : value!.year - _yearBegin;
          break;
        case 1:
          picker!.selecteds[i] = value!.month - 1;
          break;
        case 2:
          picker!.selecteds[i] = value!.day - 1;
          break;
        case 3:
          var h = value!.hour;
          if ((minHour != null && minHour! >= 0) ||
              (maxHour != null && maxHour! <= 23)) {
            if (minHour != null) {
              h = h > minHour! ? h - minHour! : 0;
            } else {
              h = (maxHour ?? 23) - (minHour ?? 0) + 1;
            }
          }
          picker!.selecteds[i] = h;
          break;
        case 4:
          if (minuteInterval == null || minuteInterval! < 2) {
            picker!.selecteds[i] = value!.minute;
          } else {
            picker!.selecteds[i] = value!.minute ~/ minuteInterval!;
            final m = picker!.selecteds[i] * minuteInterval!;
            if (m != value!.minute) {
              // 需要更新 value
              var s = value!.second;
              if (type != 2 && type != 6) s = 0;
              value = DateTime(
                  value!.year, value!.month, value!.day, value!.hour, m, s);
            }
          }
          break;
        case 5:
          picker!.selecteds[i] = value!.second;
          break;
        case 6:
          picker!.selecteds[i] = (value!.hour > 12 || value!.hour == 0) ? 1 : 0;
          break;
        case 7:
          picker!.selecteds[i] = value!.hour == 0
              ? 11
              : (value!.hour > 12)
                  ? value!.hour - 12 - 1
                  : value!.hour - 1;
          break;
      }
    }
  }

  @override
  void doSelect(int column, int index) {
    int year, month, day, h, m, s;
    year = value!.year;
    month = value!.month;
    day = value!.day;
    h = value!.hour;
    m = value!.minute;
    s = _existSec ? value!.second : 0;

    int colType = getColumnType(column);
    switch (colType) {
      case 0:
        year = _yearBegin + index;
        break;
      case 1:
        month = index + 1;
        break;
      case 2:
        day = index + 1;
        break;
      case 3:
        h = index + (minHour ?? 0);
        break;
      case 4:
        m = (minuteInterval == null || minuteInterval! < 2)
            ? index
            : index * minuteInterval!;
        break;
      case 5:
        s = index;
        break;
      case 6:
        if (picker!.selecteds[_colAP] == 0) {
          if (h == 0) h = 12;
          if (h > 12) h = h - 12;
        } else {
          if (h < 12) h = h + 12;
          if (h == 12) h = 0;
        }
        if (minHour != null || maxHour != null) {
          if (minHour != null && _colHour >= 0) {
            if (h < minHour!) {
              picker!.selecteds[_colHour] = 0;
              picker!.updateColumn(_colHour);
              return;
            }
          }
          if (maxHour != null && h > maxHour!) h = maxHour!;
        }
        break;
      case 7:
        h = index +
            (minHour == null
                ? 0
                : (picker!.selecteds[_colAP] == 0 ? minHour! : 0)) +
            1;
        if (_colAP >= 0 && picker!.selecteds[_colAP] == 1) h = h + 12;
        if (h > 23) h = 0;
        break;
    }
    int __day = _calcDateCount(year, month);

    bool _isChangeDay = false;
    if (day > __day) {
      day = __day;
      _isChangeDay = true;
    }
    value = DateTime(year, month, day, h, m, s);

    if (_verificationMinMaxValue()) {
      notifyDataChanged();
    } else if (_isChangeDay && _colDay >= 0) {
      doShow();
      picker!.updateColumn(_colDay);
    }
  }

  bool _verificationMinMaxValue() {
    DateTime? _minV = minValue;
    DateTime? _maxV = maxValue;
    if (_minV == null && yearBegin != null) {
      _minV = DateTime(yearBegin!, 1, 1, minHour ?? 0);
    }
    if (_maxV == null && yearEnd != null) {
      _maxV = DateTime(yearEnd!, 12, 31, maxHour ?? 23, 59, 59);
    }
    if (_minV != null &&
        (value!.millisecondsSinceEpoch < _minV.millisecondsSinceEpoch)) {
      value = _minV;
      return true;
    } else if (_maxV != null &&
        value!.millisecondsSinceEpoch > _maxV.millisecondsSinceEpoch) {
      value = _maxV;
      return true;
    }
    return false;
  }

  int _getAPColIndex() {
    List<int> items = customColumnType ?? columnType[type];
    _colHour = items.indexWhere((e) => e == 7);
    _colDay = items.indexWhere((e) => e == 2);
    for (int i = 0; i < items.length; i++) {
      if (items[i] == 6) return i;
    }
    return -1;
  }

  int _calcDateCount(int year, int month) {
    switch (month) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        return 31;
      case 2:
        {
          if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            return 29;
          }
          return 28;
        }
    }
    return 30;
  }

  String intToStr(int v) {
    return (v < 10) ? "0$v" : "$v";
  }

  String _checkStr(String? v) {
    return v == null ? "" : v;
  }
}

单选选择器

  //选择器
  oneOptionPicker(String title, List<String> data, String type,
      List<AttributeModel> modelList, int defaultSelect, int itemPosition) {
    SecondPickerTool.showStringPicker(context,
        title: title,
        cancelText: '',
        confirmText: '',
        normalIndex: defaultSelect,
        data: data, clickCallBack: (int selectIndex, Object selectStr) {
      int indexI = 0;
      //获取那条数据的ID
      for (int i = 0; i < modelList.length; i++) {
        if (selectStr == modelList[i].text) {
          indexI = i;
        }
      }
      //网络请求,带上ID,带上类型
      modifyMoreInfo(
          modelList[indexI].id ?? 0,
          type,
          modelList[indexI].parameter ?? '',
          indexI,
          itemPosition,
          modelList[indexI].text);
    });
  }

二级联动选择器

  //地区选择
  showPickerDialog(
      String adapterData,
      String title,
      List<AttributeModel> modelList,
      List<AttributeModel> regionAllList,
      int defaultSelect,
      int regionDefaultIndex) {
    List<int> normalIndexList = [defaultSelect, regionDefaultIndex];
    SecondPickerTool.showArrayPicker(context,
        adapter: PickerDataAdapter<String>(
            pickerdata: const JsonDecoder().convert(adapterData)),
        title: title,
        cancelText: '',
        confirmText: '',
        normalIndex: normalIndexList,
        clickCallBack: (List<int> picker, List<dynamic> selecteds) {
      //获取那条数据的ID,国家和地区的下标
      int indexI = 0;
      int indexA = 0;
      for (int i = 0; i < modelList.length; i++) {
        if (selecteds[0] == modelList[i].text) {
          for (int a = 0; a < regionAllList.length; a++) {
            if (selecteds[1] == regionAllList[a].text) {
              indexI = i;
              indexA = a;
            }
          }
        }
      }
      //网络请求,带上ID,带上类型
      modifyMoreInfoRegion(modelList[indexI].id, regionAllList[indexA].id,
          modelList[indexI].parameter, regionAllList[indexA].parameter);
    }, data: []);
  }
 类似资料: