So I want to build a slider where users can choose a value between 100-1000 and the value should be like this 100,150,200,250... 950,1000 but I noticed that I get values like 499.9999994 or 500.000001. Any idea how can I fix this?
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('100'),
CupertinoSlider(
value: context.watch<LoanValue>().value,
min: 100,
max: 1000,
divisions: 18,
onChanged: (double value) {
context.read<LoanValue>().updateValue(value);
},
),
Text('1000'),
],
),
this is where I update the value:
class LoanValue with ChangeNotifier, DiagnosticableTreeMixin {
double loanValue = 100;
double get value => loanValue;
void updateValue(double division) {
loanValue = division;
// debugPrint('loanValue: $loanValue');
notifyListeners();
}
}
I fixed this by adding this .roundToDouble()
void updateValue(double division) {
loanValue = division.roundToDouble();
// debugPrint('loanValue: $loanValue');
notifyListeners();
}
Related
I am currently creating a dropdown where the value of it should by dynamic depending the selected value that I am going to use in different widget. This is my current dropdown stateful widget:
periodic_modal.dart
extension StringExtension on String {
String capitalize() {
return "${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
}
}
class DropDown1 extends StatefulWidget {
DropDown1({super.key});
#override
State<DropDown1> createState() => _DropDown1State();
}
class _DropDown1State extends State<DropDown1> {
String? selectedMonth;
String? selectedYear;
#override
Widget build(BuildContext context) {
print("Selection month = ${Selection.currMonth}");
return Row(
children: [
DropdownButton(
// isExpanded: true,
hint: Text("Pilih Bulan"),
underline: Container(
height: 2,
color: Colors.black,
),
icon: Visibility(visible: false, child: Icon(Icons.arrow_downward)),
items: months
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
value: Selection.currMonth.capitalize().isEmpty?null:Selection.currMonth.capitalize(),
onChanged: (value) {
setState(() {
selectedMonth = value as String;
Selection.currMonth = value as String;
Selection.nextMonth = value as String;
});
},
),
SizedBox(
width: 50,
),
DropdownButton(
underline: Container(
height: 2,
color: Colors.black,
),
icon: Visibility(visible: false, child: Icon(Icons.arrow_downward)),
items: years
.map((item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: const TextStyle(
fontSize: 14,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
))
.toList(),
hint: Text("Pilih Tahun"),
value: Selection.currYear == -1 ? null : Selection.currYear.toString(),
onChanged: (value) {
setState(() {
// selectedYear = value as String;
Selection.currYear = value as int;
print("value = ${value} selection currYear = ${Selection.currYear}");
print("Selection.currYear = ${Selection.currYear}");
Selection.nextYear = value as int;
print("Selection.nextYear = ${Selection.nextYear}");
});
})
],
);
}
}
home_page.dart (Part of this whole file)
class Selection{
static int _currYear = 0;
static String _currMonth = "";
static int _nextYear = 0;
static String _nextMonth = "";
static int get currYear => _currYear;
static String get currMonth => _currMonth;
static int get nextYear => _nextYear;
static String get nextMonth => _nextMonth;
static set currYear(int value) => _currYear = value;
static set currMonth(String value) => _currMonth = value;
static set nextYear(int value) => _nextYear = value;
static set nextMonth(String value) => _nextMonth = value;
}
after I did a small debugging, I have an inkling that there is something wrong on this part of code within periodic_model.dart
onChanged: (value) {
setState(() {
// selectedYear = value as String;
Selection.currYear = value as int;
print("value = ${value} selection currYear = ${Selection.currYear}");
print("Selection.currYear = ${Selection.currYear}");
Selection.nextYear = value as int;
print("Selection.nextYear = ${Selection.nextYear}");
});
})
if I write print("value = ${value} selection currYear = ${Selection.currYear}"); above Selection.currYear = value as int; it prints successfully before I get the error. But if I did it the way I do it in the snippet - I got the error without print the print, therefore I assume there is something wrong in Selection.currYear = value as int; although I am not 100% sure.
How should I fix this?
//Edit
this is the list for years
final List<String> years = [
'2022',
'2021',
'2020',
'2019',
'2018',
'2017',
'2016',
'2015',
'2014',
'2013',
'2012',
'2011',
'2010',
'2009',
];
//Edit 2:
This is the class for Selection that is placed in home_page.dart
class Selection{
static List<List<Map<String,String>>> dataDummy = dummy;
static int _currYear = 0;
static String _currMonth = "";
static int _nextYear = 0;
static String _nextMonth = "";
static int get currYear => _currYear;
static String get currMonth => _currMonth;
static int get nextYear => _nextYear;
static String get nextMonth => _nextMonth;
static set currYear(int value) => _currYear = value;
static set currMonth(String value) => _currMonth = value;
static set nextYear(int value) => _nextYear = value;
static set nextMonth(String value) => _nextMonth = value;
}
I guess years data is List<String>.
int.parse(value);
Insert upper line before Selection.currYear = value as int;
Hope to working well. Happy Coding 🧑💻
I managed to find the answer, however I am not entirely sure if my approach is the right one or not but for this particular problem, it manages to fix it.
Within setState() on the line of
Selection.currYear = value as int;
I change it to
Selection.currYear = int.parse(value??"0");
and I also did it for
Selection.nextYear = value as int;
too
I am trying to build a dropdownbutton in flutter, but I am getting an error
type 'String' is not a subtype of type 'MorphShape' of 'function result'
I have a class:
class MorphShape {
Shape value;
String name;
MorphShape(this.value, this.name);
}
I init a list of possible values for the dropdown
final List<MorphShape> morphShapes = [
MorphShape(Shape.rect, 'rect'),
MorphShape(Shape.cross, 'cross'),
MorphShape(Shape.ellipse, 'ellipse')
];
late MorphShape morphKernelShape = morphShapes[2];
and finally setup the dropdown
Center(
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 25),
child: DropdownButton(
value: morphKernelShape,
onChanged: (MorphShape? morphShape) {
setState(() {
morphKernelShape = morphShape!;
});
},
items: morphShapes.map<DropdownMenuItem<MorphShape>>(
(MorphShape value) {
return DropdownMenuItem(
value: value, child: Text(value.name));
}).toList(),
),
)),
The IDE itself doesn't highlight anything as a problem, but when I try to run my app it gives me the above stated error. I can't seem to figure out what is the problem here?
Here you're passing the MorphShape object to the value which accepts String :
value: morphKernelShape, // this should be String
onChanged: (MorphShape? morphShape) {
setState(() {
morphKernelShape = morphShape!; // here you're passing to it MorphShape object.
});
// ...
maybe you wanted to do this instead:
value: morphKernelShape.name,
The code works, if anyone else runs into a similar issue, it most likely is due to hot reload.
I have built a custom slider and have been using GestureDetector with onHorizontalDragUpdate to report drag changes, update the UI and value.
However, when a user lifts their finger, there can sometimes be a small, unintentional hop/drag, enough to adjust the value on the slider and reduce accuracy. How can I stop this occuring?
I have considered adding a small delay to prevent updates if the drag hasn't moved for a tiny period and assessing the primaryDelta, but unsure if this would be fit for purpose or of there is a more routine common practive to prevent this.
--
Example of existing drag logic I am using. The initial drag data is from onHorizontalDragUpdate in _buildThumb. When the slider is rebuilt, the track size and thumb position is calculated in the LayoutBuilder and then the value is calculated based on the thumb position.
double valueForPosition({required double min, required double max}) {
double posIncrements = ((max) / (_divisions));
double posIncrement = (_thumbPosX / (posIncrements));
double incrementVal =
(increment) * (posIncrement + widget.minimumValue).round() +
(widget.minimumValue - widget.minimumValue.truncate());
return incrementVal.clamp(widget.minimumValue, widget.maximumValue);
}
double thumbPositionForValue({required double min, required double max}) {
return (max / (widget.maximumValue - widget.minimumValue - 1)) *
(value - widget.minimumValue - 1);
}
double trackWidthForValue({
required double min,
required double max,
required double thumbPosition,
}) {
return (thumbPosition + (_thumbTouchZoneWidth / 2))
.clamp(min, max)
.toDouble();
}
bool isDragging = false;
bool isSnapping = false;
Widget _buildSlider() {
return SizedBox(
height: _contentHeight,
child: LayoutBuilder(
builder: (context, constraints) {
double minThumbPosX = -(_thumbTouchZoneWidth - _thumbWidth) / 2;
double maxThumbPosX =
constraints.maxWidth - (_thumbTouchZoneWidth / 2);
if (isDragging) {
_thumbPosX = _thumbPosX.clamp(minThumbPosX, maxThumbPosX);
value = valueForPosition(min: minThumbPosX, max: maxThumbPosX);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.onChanged(value);
});
} else {
_thumbPosX = thumbPositionForValue(
min: minThumbPosX,
max: maxThumbPosX,
);
}
double minTrackWidth = 0;
double maxTrackWidth = constraints.maxWidth;
double trackWidth = 0;
if (isDragging) {
trackWidth = (_thumbPosX + (_thumbTouchZoneWidth / 2))
.clamp(_thumbWidth, constraints.maxWidth);
} else {
trackWidth = trackWidthForValue(
min: minTrackWidth,
max: maxTrackWidth,
thumbPosition: _thumbPosX,
);
}
return Stack(
alignment: Alignment.centerLeft,
clipBehavior: Clip.none,
children: [
_buildLabels(),
_buildInactiveTrack(),
Positioned(
width: trackWidth,
child: _buildActiveTrack(),
),
Positioned(
left: _thumbPosX,
child: _buildThumb(),
),
],
);
},
),
);
}
Widget _buildThumb() {
return GestureDetector(
behavior: HitTestBehavior.opaque,
dragStartBehavior: DragStartBehavior.down,
onHorizontalDragUpdate: (details) {
setState(() {
_thumbPosX += details.delta.dx;
isDragging = true;
});
},
child: // Thumb UI
);
}
Updated: I make a little adjustment by adding a delay state and lastChangedTime.
If the user stops dragging for a short period (3 sec), the slider will be locked until the next new value is updated + a short delay (1.5 sec)
I follow your train of thought and make a simple example from Slider widget.
Is the result act like your expected? (You can adjust the Duration to any number)
DartPad: https://dartpad.dev/?id=95f2bd6d004604b3c37f27dd2852cb31
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
double _currentSliderValue = 20;
DateTime lastChangedTime = DateTime.now();
bool isDalying = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(_currentSliderValue.toString()),
const SizedBox(height: 30),
Slider(
value: _currentSliderValue,
max: 100,
label: _currentSliderValue.round().toString(),
onChanged: (double value) async {
if (isDalying) {
await Future.delayed(
Duration(milliseconds: 1500),
() => isDalying = false,
);
} else {
if (DateTime.now().difference(lastChangedTime) >
Duration(seconds: 3)) {
isDalying = true;
} else {
setState(() {
_currentSliderValue = value;
});
}
}
lastChangedTime = DateTime.now();
},
),
],
);
}
}
I would like to know how to implement this code, with three input variables and three different solutions related to each dropdown option. Each time i choose an option, does it block the entry that is not part of the equation and return the calculation value of the selected item?
var _currencies = ['A', 'B', 'C'];
DropdownButtonHideUnderline( child: DropdownButton<String>(
items: _currencies.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
value: _currentItemSelected,
isDense: true,
onChanged: (String newValueSelected) {
//code a executar
_onDropDownItemSelected(
newValueSelected);
},
))
String _calculateTotalReturns() {
double entry1 = double.parse(entry1Controller.text);
double entry2 = double.parse(entry2Controller.text);
double entry3 = double.parse(entry3Controller.text);
//Three equations for each dropdown option
double total1 = entry1 * entry2; //FOR DROPDOWN A
double total2 = entry1 + entry2 + entry3 //FOR DROPDOWN B
double total3 = entry1*entry2+entry3 //FOR DROPDOWN C
String result = '';
return result; // How to return result for curerrent dropdown selected when i aply a calculate button???
}
//--------------------------------------------------------------------
child: RaisedButton(
child: Text(
'Calcular',
textScaleFactor: 1.5,
),
color: Theme.of(context).accentColor,
textColor: Theme.of(context).primaryColorDark,
onPressed: () {
setState(() {
if (_formKey.currentState.validate()) {
this.displayResult =
_calculateTotalReturns();
}
});
},
),
You will need to have a global variable which will store the value which value user selected from the dropdown. For example,
String selectedValue; // <-- this will be a global variable.
Change the global variable on onChanged
onChanged: (String newValueSelected) {
selectedValue = newValueSelected;
}
Then you can access this variable in the _calculateTotalReturns() method and return the correct result accordingly like..
String _calculateTotalReturns() {
//other code
String result;
if(selectedValue == 'A'){
result = total1.toString();
}
else if(selectedValue == 'B'){
result = total2.toString();
}
else{
result = total3.toString();
}
return result;
}
My slider jumps from one value to another. How can I make it movable in between the values or make it smoother in Flutter?
Column(
children: <Widget>[
RangeSlider(
min: 1,
max: 40,
divisions: 4,
onChanged: onChange,
values: RangeValues((startValue ?? 1).toDouble(), (endValue ?? 40).toDouble()),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text('Beginner', style: Theme.of(context).textTheme.body2),
Text('Ameateur', style: Theme.of(context).textTheme.body2),
Text('Intermediate', style: Theme.of(context).textTheme.body2),
Text('Advanced', style: Theme.of(context).textTheme.body2),
Text('Professional', style: Theme.of(context).textTheme.body2),
],
),
],
);
The divisions property makes it jump in 4 steps. Remove that and the slider will be fluent.
RangeSlider(
min: 1,
max: 40,
// divisions: 4, // <-- remove this
onChanged: onChange,
values: RangeValues((startValue ?? 1).toDouble(), (endValue ?? 40).toDouble()),
),
Update after question clarification
Question is actually about having the thumbs being fluent while still snapping to discrete points on the range slider. The Flutter widget does not provide an easy way to achieve that, so there are two options.
Option 1: custom widget
The first is to create a new widget to get the exact behavior you need. This might be the best option if you need a lot of customisation.
Option 2: hack the RangeSlider widget
Use the onChangeEnd callback in addition to the onChanged callback to update the position of the thumb correctly. The onChanged callback is used to make it fluent, while the onChangeEnd callback is used to snap the thumb to the closest discrete value based on a division count.
A possible implementation (the discretize method can possibly be shorter/improved):
class _RangeSliderExampleState extends State<RangeSliderExample> {
double _startValue;
double _endValue;
static const double _minValue = 1;
static const double _maxValue = 40;
static const double _divisions = 4;
#override
Widget build(BuildContext context) {
return RangeSlider(
min: _minValue,
max: _maxValue,
onChanged: (values) {
setState(() {
_startValue = values.start;
_endValue = values.end;
});
},
onChangeEnd: (values) {
setState(() {
_startValue = _discretize(values.start, _divisions);
_endValue = _discretize(values.end, _divisions);
});
},
values: RangeValues((_startValue ?? 1).toDouble(),
(_endValue ?? 40).toDouble()),
);
}
double _discretize(double value, double divisions) {
double x = value - _minValue;
double range = _maxValue - _minValue;
double result = x / range;
return (result * divisions).round() / divisions * range + _minValue;
}
}