Flutter's built-in Slider widget handles a basic range input in just a few lines — customizing its visual appearance beyond the default Material styling needs SliderTheme wrapped around it.
A basic slider
double _volume = 50;
Slider(
value: _volume,
min: 0,
max: 100,
onChanged: (value) {
setState(() {
_volume = value;
});
},
)
onChanged fires continuously as the slider is dragged — setState() inside it is what keeps the widget's displayed position (and any other UI depending on _volume) in sync with the current drag position in real time.
Showing the current value as a label
Slider(
value: _volume,
min: 0,
max: 100,
divisions: 10,
label: _volume.round().toString(),
onChanged: (value) {
setState(() {
_volume = value;
});
},
)
divisions snaps the slider to discrete steps rather than a continuous range, and label shows a small tooltip with the current value above the thumb while dragging — both need to be set together for the label to display, since a continuous (non-divided) slider doesn't show one by default.
Handling drag-start and drag-end separately from onChanged
Slider(
value: _volume,
onChanged: (value) => setState(() => _volume = value),
onChangeStart: (value) => print('Started dragging at $value'),
onChangeEnd: (value) => print('Finished dragging at $value'),
)
onChangeEnd is genuinely useful for deferring an expensive operation (like an API call to save the new value) until the user has actually finished dragging, rather than firing it on every intermediate value during onChanged.
Customizing a slider's appearance with SliderTheme
SliderTheme(
data: SliderThemeData(
trackHeight: 8,
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.deepPurple.shade100,
thumbColor: Colors.deepPurple,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12),
overlayColor: Colors.deepPurple.withOpacity(0.2),
),
child: Slider(
value: _volume,
onChanged: (value) => setState(() => _volume = value),
),
)
SliderTheme is the mechanism for styling every visual aspect of the slider independently — track thickness and color, thumb size and color, and the ripple-like overlay shown while dragging — none of which the plain Slider widget exposes as direct constructor parameters.
A range slider, for selecting a min and max value together
RangeValues _priceRange = const RangeValues(20, 80);
RangeSlider(
values: _priceRange,
min: 0,
max: 100,
divisions: 20,
labels: RangeLabels(
_priceRange.start.round().toString(),
_priceRange.end.round().toString(),
),
onChanged: (values) {
setState(() {
_priceRange = values;
});
},
)
RangeSlider, with two thumbs instead of one, is the correct widget for a "min to max" style filter (like a price range) — attempting to fake this with two separate regular Slider widgets would need significant extra logic to keep the two thumbs from crossing each other.