Open magicmatt007 opened 1 year ago
在PC和web上都有类似的问题。不能准确地控制滚动,而是一次跳几个。
我采用了一个比较丑的解决方法,在PC上,套上一层GestureDetector,用来支持拖拉,从而精确控制。
Me too!Any solutions?I increased the itemExtent parameter and it solved the problem, but when I run it on a large-screen computer, the problem still exists.
I solved it,Related to this:https://github.com/flutter/flutter/issues/119500. First localize flutter_picker: 2.1.0, just copy the four files in the lib directory to your project, and then replace the code of the picker.dart file with the following code: import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/material.dart' as Dialog; import 'dart:async'; import 'picker_localizations.dart';
/// Picker selected callback.
typedef PickerSelectedCallback = void Function(
Picker picker, int index, List
/// Picker confirm callback.
typedef PickerConfirmCallback = void Function(
Picker picker, List
/// Picker confirm before callback.
typedef PickerConfirmBeforeCallback = Future
/// Picker value format callback.
typedef PickerValueFormat
/// 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 Picker { static const double DefaultTextSize = 18.0;
/// Index of currently selected items
late List
/// Picker adapter, Used to provide data and generate widgets late PickerAdapter adapter;
/// insert separator before picker columns
final List
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
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;
final bool printDebug;
Widget? _widget; PickerWidgetState? _state;
Picker(
{required this.adapter,
this.delimiter,
List
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), 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
/// show dialog picker
Future<List
/// 获取当前选择的值 /// Get the value of the current selection List getSelectedValues() { return adapter.getSelectedValues(); }
/// 取消
void doCancel(BuildContext context) {
Navigator.of(context).pop<List
/// 确定
void doConfirm(BuildContext context) async {
if (onConfirmBefore != null && !(await onConfirmBefore!(this, selecteds))) {
return; // Cancel;
}
Navigator.of(context).pop<List
/// 弹制更新指定列的内容 /// 当 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: Picker.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
/// 数据值 final T? value;
/// 子项
final List<PickerItem
PickerItem({this.text, this.value, this.children}); }
class PickerWidget
static PickerWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType
class _PickerWidget
@override
PickerWidgetState createState() =>
PickerWidgetState
class PickerWidgetState
ThemeData? theme;
final List
@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?.bottomAppBarTheme.color
: picker.headerColor,
),
));
}
}
_body.add(_wait
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildViews(),
)
: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildViews(),
),
));
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
List
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 ?? theme!.textTheme.titleLarge)
?.copyWith(
fontSize: Picker.DefaultTextSize,
) ??
TextStyle(fontSize: Picker.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: Picker._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
List<Widget> items = [];
PickerAdapter? adapter = picker.adapter;
adapter.setColumn(-1);
final _decoration = BoxDecoration(
color: picker.containerColor == null
? theme!.dialogBackgroundColor
: picker.containerColor,
);
if (adapter.length > 0) {
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 (picker.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 (picker.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: DecoratedBox(
decoration: _decoration,
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;
}
Map m = {};
Widget _buildCupertinoPicker(BuildContext context, int i, int _length, PickerAdapter adapter, Key? key) { if(!m.containsKey(i)){ //isMouseWheel:为了维持鼠标拖拽时的丝滑,如果不做这个处理,那么鼠标拖拽时也会每次只能跳一格 m[i] = {"isMouseWheel":false,"indexCorrect":scrollController[i].initialItem,}; } return Listener( onPointerSignal: (PointerSignalEvent event) { if (event is PointerScrollEvent) { m[i]["isMouseWheel"] = true; } }, child: 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 selectedIndex) { if (_length <= 0) return; if(m[i]["isMouseWheel"] == true){ if (selectedIndex > m[i]["indexCorrect"]) { m[i]["indexCorrect"] = m[i]["indexCorrect"] + 1; scrollController[i].jumpToItem(m[i]["indexCorrect"]); } else if (selectedIndex < m[i]["indexCorrect"]) { scrollController[i].jumpToItem(--m[i]["indexCorrect"]); } m[i]["isMouseWheel"] = false; }else{ m[i]["indexCorrect"] = selectedIndex; } var index = m[i]["indexCorrect"] % _length; if (picker.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
/// 选择器数据适配器
abstract class PickerAdapter
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) { final theme = picker!.textStyle != null || picker!.state?.context == null ? null : Theme.of(picker!.state!.context); return Center( child: DefaultTextStyle( overflow: TextOverflow.ellipsis, maxLines: 1, textAlign: picker!.textAlign, style: picker!.textStyle ?? TextStyle( color: theme?.brightness == Brightness.dark ? Colors.white : Colors.black87, fontFamily: theme == null ? "" : theme.textTheme.headline6?.fontFamily, fontSize: Picker.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
return Center(
child: DefaultTextStyle(
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: picker!.textAlign,
style: picker!.textStyle ??
TextStyle(
color: _txtColor,
fontSize: _txtSize,
fontFamily: theme == null
? ""
: theme.textTheme.headline6?.fontFamily),
child: Wrap(
children: items,
)));
}
String getText() { return getSelectedValues().toString(); }
List
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
PickerDataAdapter(
{List? pickerData, List<PickerItem
@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 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 (picker?.printDebug == true) print("data.length: ${data.length}");
}
_parsePickerDataItem(List? pickerData, List
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 (picker!.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 makeText(_v, null, isSel); } if (item.text != null) { return isSel && picker!.selectedTextStyle != null ? DefaultTextStyle( style: picker!.selectedTextStyle!, textAlign: picker!.textAlign, 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 =
@override
List
_checkPickerDataLevel(List
class NumberPickerColumn {
final List
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
final List
@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 =
@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 makeText(_v, null, isSel); } 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
/// 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
/// Whether to display the month in numerical form.If true, months is not used. final bool isNumberMonth;
/// custom months strings
final List
/// Custom AM, PM strings
final List
/// 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
static const List
static const List
DateTimePickerAdapter({
Picker? 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
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
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
// static const List
// 获取当前列的类型
int getColumnType(int index) {
if (customColumnType != null) return customColumnType![index];
List
// 判断是否存在秒 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 =
@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: final apStr = strAMPM ?? PickerLocalizations.of(context).ampm ?? const ['AM', 'PM']; _text = "${apStr[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 makeText(_v, null, isSel);
}
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(); final sh = value!.hour; 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 = sh; 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: // minute 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; final h = _colAP >= 0 ? _calcHourOfAMPM(sh, m) : sh; value = DateTime(value!.year, value!.month, value!.day, h, m, s); } } break; case 5: picker!.selecteds[i] = value!.second; break; case 6: // am/pm picker!.selecteds[i] = (sh > 12 || (sh == 12 && (value!.minute > 0 || value!.second > 0))) ? 1 : 0; break; case 7: picker!.selecteds[i] = sh == 0 ? 11 : (sh > 12) ? sh - 12 - 1 : sh - 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!;
if (_colAP >= 0) {
h = _calcHourOfAMPM(h, m);
}
break;
case 5:
s = index;
break;
case 6:
h = _calcHourOfAMPM(h, m);
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) {
h = _calcHourOfAMPM(h, m);
}
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; }
// Calculate am/pm time transfer int _calcHourOfAMPM(int h, int m) { // 12:00 AM , 00:00:000 // 12:30 AM , 12:30:000 // 12:00 PM , 12:00:000 // 12:30 PM , 00:30:000 if (picker!.selecteds[_colAP] == 0) { // am if (h == 12 && m == 0) { h = 0; } else if (h == 0 && m > 0) { h = 12; } if (h > 12) h = h - 12; } else { // pm if (h > 0 && h < 12) h = h + 12; if (h == 12 && m > 0) { h = 0; } else if (h == 0 && m == 0) { h = 12; } } return h; }
int _getAPColIndex() {
List
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; } }
Hi,
OS: Windows. Topic: Select a number form a NumberPickerColumn with the mouse wheel Problem: Every move of the mouse wheel results in a jump of 3 values. It is not possible to select every value from the picker. Note: jump is already set to 1. If I set it e.g. to 5, the actual jumps are 5x3 = 15
Any solutions?
Thx