第三方库的选择器样式跟公司产品需求不一样,需要更改第三方库源码
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>(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>(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 leapYearMonths = const [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: []);
}