For one of my projects I was looking for a NumberPicker to give the user the opportunity to select a specific duration. I found the great numberpicker 1.0.0 package from Marcin Szalek . NumberPicker is a custom widget designed for choosing an integer or decimal number by scrolling spinners. While this widged worked like intended, I was in need for a more general approach, a picker, wherer the user can choose from a predefined set of values. So I decided to sepearate values from it’s visual appearance. You have to inititalize the widget with a List of text/value pairs List<ValuePickerItem>
.
For example a list of durations would look like this:
text | value in seconds (in this case int) |
“00:30” | 30 |
“01:00” | 60 |
“02:00” | 120 |
“05:00” | 300 |
“10:00” | 600 |
ValuePicker is heavily inspered by NumberPicker and shares some code, so please check the LICENSE AGREEMENTS of NumberPicker 1.0.0 of the source before you use it.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ValuePickerItem {
String text;
dynamic value;
ValuePickerItem(this.text, this.value);
}
class ValuePicker extends StatefulWidget {
final List<ValuePickerItem> itemList;
final ValueChanged<num> onChanged;
final int initialIndex;
final double itemHeight;
final double width;
final int extraLines;
ValuePicker({
Key key,
@required this.itemList,
@required this.initialIndex,
@required this.onChanged,
this.itemHeight = 50.0,
this.width = 100.0,
this.extraLines = 1,
}) :
assert (itemList != null),
assert (initialIndex != null),
assert (onChanged != null),
super(key: key);
@override
ValuePickerState createState() {
return new ValuePickerState();
}
}
class ValuePickerState extends State<ValuePicker> {
int _currentIndex;
int _itemCount;
int _listItemCount;
double _listViewHeight;
ScrollController _scrollController;
TextStyle _defaultStyle;
TextStyle _selectedStyle;
animateToIndex(int index) {
_animate(_scrollController, index * widget.itemHeight);
}
_animate(ScrollController scrollController, double value) {
scrollController.animateTo(value, duration: Duration(seconds: 1), curve: ElasticOutCurve());
}
bool _userStoppedScrolling(Notification notification, ScrollController scrollController) {
return notification is UserScrollNotification &&
notification.direction == ScrollDirection.idle &&
scrollController.position.activity is! HoldScrollActivity;
}
bool _onIndexNotification(Notification notification) {
if (notification is ScrollNotification) {
int newIndex = (notification.metrics.pixels / widget.itemHeight).round() + widget.extraLines;
if (_userStoppedScrolling(notification, _scrollController)) animateToIndex(newIndex - widget.extraLines);
if (newIndex != _currentIndex) {
setState(() {
_currentIndex = newIndex;
});
widget.onChanged(_currentIndex - widget.extraLines);
}
}
return true;
}
@override
void initState() {
_currentIndex = widget.initialIndex + widget.extraLines;
_listViewHeight = widget.itemHeight * (widget.extraLines * 2 + 1);
_itemCount = widget.itemList.length;
_listItemCount = _itemCount + widget.extraLines * 2;
_scrollController = ScrollController( initialScrollOffset: ((_currentIndex - widget.extraLines) * widget.itemHeight).toDouble());
super.initState();
}
///main widget
@override
Widget build(BuildContext context) {
_defaultStyle = Theme.of(context).textTheme.body1;
_selectedStyle = Theme.of(context).textTheme.headline.copyWith(color: Theme.of(context).accentColor);
return _buildListView();
}
Widget _buildItem(BuildContext context, int index) {
if ( index < widget.extraLines || index >= (_listItemCount - widget.extraLines)) {
return Container();
} else {
String value = widget.itemList[index - widget.extraLines].text;
final TextStyle itemStyle = (index == _currentIndex ? _selectedStyle : _defaultStyle);
return Center( child: Text(value, style: itemStyle));
}
}
Widget _buildListView() {
return NotificationListener(
child: Container(
height: _listViewHeight,
width: widget.width,
child: ListView.builder(
controller: _scrollController,
itemExtent: widget.itemHeight,
itemCount: _listItemCount,
itemBuilder: _buildItem,
),
),
onNotification: _onIndexNotification,
);
}
}
Usage
...
List<ValuePickerItem> _durationList = List<ValuePickerItem>();
_durationList.add(ValuePickerItem("00:30", 30));
_durationList.add(ValuePickerItem("01:00", 60));
_durationList.add(ValuePickerItem("02:00", 120));
_durationList.add(ValuePickerItem("05:00", 300));
_durationList.add(ValuePickerItem("10:00", 600));
int _pickerIndex = 2;
...
ValuePicker(
itemList: _durationList ,
initialIndex: _pickerIndex ,
onChanged: (newIndex) {
_pickerIndex = _durationList[newIndex].value;
},
extraLines: 2,
width: 60,
),
This widget works fine for me but is not testet on different devices.