Issue
i try using Slider().. divisions look good (value >= 50 ? 10 : 20)
But, How to add tick and label above slider ?
Expect :
tick will be change color following position of slider
Actual:
Slider(
min: 0,
max: 100,
value: value,
onChanged: (val) {
setState(() {
value = val;
});
},
divisions: value >= 50 ? 10 : 20,
label: value.toString(),
),
my problem is :
- position of label and tick (if using column)
- Change tick color if position of slider is same
my code using Column(
Column(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 20),
child:
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(6, (index) => Text('$index')),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(
16,
(index) => SizedBox(
height: 8,
child: VerticalDivider(
width: 8,
color: HelperColors.orange,
),
),
),
)
],
),
),
Slider(
value: widget.value,
min: widget.minValue,
max: widget.maxValue,
divisions: widget.divisions,
onChanged: widget.onChanged,
label: widget.value.toString(),
),
],
);
could you help me to fix some problem on this design?
Solution
Ticks and Tick Value Layout
Instead of wrapping rows of ticks and tick values in a column, it may be better to combine both tick and tick value as a single column. Then, wrap this column with Expanded
widget and put it in List.generate
of a row. This guarantees the tick always aligns with the tick value and each column has equal spacing.
Ticks and Slider Alignment
The slider comes with offset by default to make room for thumb and overlay. Even though you precisely calculate the spacing between ticks on your screen, the spacing will still stretch when it is tested on different screen size.
Please check this link if you are interested in learning more about Slider offset/ margin.
Slider offset issue
Here what you need to do to achieve perfect alignment
Remove the offset by creating custom
trackShape
of slider.Measure the offset of one tick spacing and divide it by 2.
The formula isMediaQuery.of(context).size.width / numOfTick / 2
Wrap zero-offset slider with
Padding
widget and use the calculated offset as horizontal padding value.padding: EdgeInsets.symmetric(horizontal: offset),
offset V |---| .----------/ /------------. | 0 | | 100 | | |Ticks area | | | | | | | || .----------/ /------------.| | || | Slider area ||Screen edge | || '----------/ /-------------'|
Now ticks and slider will always be perfectly aligned, edge-to-edge, regardless screen size.
Here's the example. You may still want to improve the UI as per your requirements.
What's supported
- Adjustable major and minor ticks
- Tick highlighting when value matches
- Value precision control
double value = 50;
double actualValue = 50;
double minValue = 0;
double maxValue = 100;
List<double> steps = [0,5,10,15,20,25,30,35,40,45,50,60,70,80,90,100];
// ...
CustomSlider(
minValue: minValue,
maxValue: maxValue,
value: value,
majorTick: 6,
minorTick: 2,
labelValuePrecision: 0,
tickValuePrecision: 0,
onChanged: (val) => setState(() {
value = val;
actualValue =
steps[(val / maxValue * (steps.length - 1)).ceil().toInt()];
print('Slider value (linear): $value');
print('Actual value (non-linear): $actualValue');
}),
activeColor: Colors.orange,
inactiveColor: Colors.orange.shade50,
linearStep: false,
steps: steps,
),
Custom slider widget
class CustomSlider extends StatelessWidget {
final double value;
final double minValue;
final double maxValue;
final int majorTick;
final int minorTick;
final Function(double)? onChanged;
final Color? activeColor;
final Color? inactiveColor;
final int labelValuePrecision;
final int tickValuePrecision;
final bool linearStep;
final List<double>? steps;
CustomSlider({
required this.value,
required this.minValue,
required this.maxValue,
required this.majorTick,
required this.minorTick,
required this.onChanged,
this.activeColor,
this.inactiveColor,
this.labelValuePrecision = 2,
this.tickValuePrecision = 1,
this.linearStep = true,
this.steps,
});
@override
Widget build(BuildContext context) {
final allocatedHeight = MediaQuery.of(context).size.height;
final allocatedWidth = MediaQuery.of(context).size.width;
final divisions = (majorTick - 1) * minorTick + majorTick;
final double valueHeight =
allocatedHeight * 0.05 < 41 ? 41 : allocatedHeight * 0.05;
final double tickHeight =
allocatedHeight * 0.025 < 20 ? 20 : allocatedHeight * 0.025;
final labelOffset = allocatedWidth / divisions / 2;
return Column(
children: [
Row(
children: List.generate(
divisions,
(index) => Expanded(
child: Column(
children: [
Container(
alignment: Alignment.bottomCenter,
height: valueHeight,
child: index % (minorTick + 1) == 0
? Text(
linearStep
? '${(index / (divisions - 1) * maxValue).toStringAsFixed(tickValuePrecision)}'
: '${(steps?[index])?.toStringAsFixed(tickValuePrecision)}',
style: TextStyle(
fontSize: 12,
),
textAlign: TextAlign.center,
)
: null,
),
Container(
alignment: Alignment.bottomCenter,
height: tickHeight,
child: VerticalDivider(
indent: index % (minorTick + 1) == 0 ? 2 : 6,
thickness: 1.2,
color: (index / (divisions - 1)) * maxValue == value
? activeColor ?? Colors.orange
: Colors.grey.shade300,
),
),
],
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: labelOffset),
child: SliderTheme(
data: SliderThemeData(
trackHeight:
allocatedHeight * 0.011 < 9 ? 9 : allocatedHeight * 0.011,
activeTickMarkColor: activeColor ?? Colors.orange,
inactiveTickMarkColor: inactiveColor ?? Colors.orange.shade50,
activeTrackColor: activeColor ?? Colors.orange,
inactiveTrackColor: inactiveColor ?? Colors.orange.shade50,
thumbColor: activeColor ?? Colors.orange,
overlayColor: activeColor == null
? Colors.orange.withOpacity(0.1)
: activeColor!.withOpacity(0.1),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.0),
trackShape: CustomTrackShape(),
showValueIndicator: ShowValueIndicator.never,
valueIndicatorTextStyle: TextStyle(
fontSize: 12,
),
),
child: Slider(
value: value,
min: minValue,
max: maxValue,
divisions: divisions - 1,
onChanged: onChanged,
label: value.toStringAsFixed(labelValuePrecision),
),
),
),
],
);
}
}
class CustomTrackShape extends RoundedRectSliderTrackShape {
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final double trackHeight = sliderTheme.trackHeight!;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}
Test
Edit (slider with non-linear steps)
Because Flutter slider has linear value-position mapping, it may not be a good idea to alter the slider value based on non-linear values. However, it is still possible to achieve that by creating additional value range for actual value then map them together.
Here are the steps
- Create a list which contains non-linear values (the one you created)
- Make sure the length of the list matches the total number of tick. Otherwise, the mapping will be incorrect.
listLength = (majorTick - 1) * minorTick + majorTick
The above code has been edited to address this issue.
Test
Mapped values
Start End
Slider value (linear) 0.00 6.67 13.33 ... 86.67 93.33 100.0
Actual value (non-linear) 0.00 5.00 10.00 ... 80.00 90.00 100.0
Answered By - dave.tdv
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.