Souvik Biswas Mobile developer (Android, iOS, and Flutter), technical writer, IoT enthusiast, avid video game player.

Flutter Slider widgets: A deep dive with examples

10 min read 2939

Flutter Slider widgets: A deep dive with examples

Slider is one of the basic Flutter widgets that can be used to select from a range of values by moving the slider thumb. There are few different types of slider widgets in Flutter, and the commonly used ones that are present within the Flutter framework are:

  • Slider – A Material Design component that lets you select a single value from a range of values (one slider thumb is present)
  • CupertinoSlider – similar to Slider, but follows the Cupertino design style
  • RangeSlider – used for selecting a range (two slider thumbs are used) from a range of values

You can have a look at this video to have a quick overview of the various Slider widgets:

In this article, you’ll learn:

Let’s get into it!

Getting started

We will be checking out the various theming options that you can apply to the slider, but in its most basic form, it looks like this:

The slider component in its most basic form

The code required to achieve this is as follows:

Slider(
  min: 0.0,
  max: 100.0,
  value: _value,
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

The _value variable is initialized inside the widget class:

double _value = 20;

The properties that I’ve set above are the minimum properties that you have to define in order to build any slider using Flutter, but your properties may vary a bit for a different kind of slider. Let’s take a look at these properties:

We made a custom demo for .
No really. Click here to check it out.

  • min: The minimum value that a user can select by dragging the slider to the left (the values get smaller towards the left of the slider)
  • max: The maximum value that a user can select by dragging the slider to the right (the values get larger towards the right of the slider)
  • value: The current selected value of the slider that a user can change by dragging the slider thumb
  • onChanged: This is a callback function that gets called as the user drags the slider thumb to the right or left on the track and returns the current value of the slider position

Inside onChanged, you have to assign the updated value to the _value variable that is stored inside the class using setState:

setState(() {
  _value = value;
});

Here, setState is used to update the UI, so that each time the value updates it is reflected on the Slider widget. Please note that the parent widget inside which the Slider is present should be a StatefulWidget, otherwise you won’t be able to use setState.

This basic slider widget uses the Material Design style, which is appropriate for Android devices, while iOS devices tend to follow the Cupertino style. It’s preferable to use the CupertinoSlider for apps running on the iOS platform.

You can achieve the iOS-style slider by just replacing the Slider widget with the CupertinoSlider widget, and leaving the properties exactly the same.

The slider will look like this:

The basic Cupertino slider

The following code is used to build this slider:

Container(
  width: double.maxFinite,
  child: CupertinoSlider(
    min: 0.0,
    max: 100.0,
    value: _value,
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

By default, the Cupertino Slider doesn’t take up the entire width of the screen, so you have to wrap it with a Container widget and provide a width of double.maxFinite if you want to extend it to the width of the screen.

Both Slider and CupertinoSlider only allow you to select a single value from the specified range, but if you want to select two values you can use the RangeSlider widget.

RangeSlider widget

The RangeSlider widget follows the Material Design style, and it comes with two slider thumbs that you can use to set the start and end values. In this widget, there’s no value property; instead, it comes with a values property, which has a type of RangeValues.

A basic RangeSlider widget looks like this:

The basic RangeSlider widget

To build this, you can use the following code:

RangeSlider(
  min: 0.0,
  max: 100.0,
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

RangeValues takes a pair of two values as input: the start value (provided by _startValue) and the end value (provided by _endValue). You can define these two variables within the widget class, like this:

double _startValue = 20.0;
double _endValue = 90.0;

As you run your app using these values, the slider thumbs will be initialized as per these values. In the Range Slider, the onChanged callback function also returns RangeValues that you can use to update the slider thumb positions using this:

setState(() {
  _startValue = values.start;
  _endValue = values.end;
});

Customizing the slider color

All three of the slider widgets that we have discussed above come with a few properties that can be used to customize the color of the sliders.

The basic Slider widget gives you access to three properties for setting its color:

  • activeColor: Applies color to the active portion of the slider track
  • inactiveColor: Applies color to the inactive portion of the slider track
  • thumbColor: Applies color to the slider thumb

The Slider widget's color properties

You can achieve this Slider color combination using the following code:

Slider(
  min: 0.0,
  max: 100.0,
  activeColor: Colors.purple,
  inactiveColor: Colors.purple.shade100,
  thumbColor: Colors.pink,
  value: _value,
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

Similarly, you can change these properties and easily customize the Slider colors. Following are a few examples:

Example custom sliders

If you are using the CupertinoSlider widget, you can just customize two color properties:

  • activeColor
  • thumbColor

Below is an example of a customized Cupertino Slider:

Cupertino Slider with customized colors

You can use the following code to build the customized iOS-style slider shown above:

Container(
  width: double.maxFinite,
  child: CupertinoSlider(
    min: 0.0,
    max: 100.0,
    value: _value,
    activeColor: CupertinoColors.activeGreen,
    thumbColor: CupertinoColors.systemPink,
    divisions: 10,
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

The RangeSlider widget also allows you to customize just two properties, though they are different from the ones the Cupertino Slider allows:

  • activeColor
  • inactiveColor

Below is an example of a customized Range Slider:

A Range slider with customized colors

This slider can be built using the following code:

RangeSlider(
  min: 0.0,
  max: 100.0,
  activeColor: widget.activeColor,
  inactiveColor: widget.inactiveColor,
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

Later in this article, we will discuss more complex customizations and themes that you can apply on the sliders.

Displaying slider divisions and labels

Typically, slider widgets return decimal values because they are continuous by default. But if you need just discrete values (i.e., whole integer numbers without any decimal places), you can use the divisions property.

label is a property that is commonly used with discrete values. It is used to show the currently selected discrete value above the slider thumb when it’s active.

The basic Slider widget will look like the below when the divisions and label properties are set:

A basic Slider widget with the divisions and labels props applied

The code for this slider is as follows:

Slider(
  min: 0.0,
  max: 100.0,
  value: _value,
  divisions: 10,
  label: '${_value.round()}',
  onChanged: (value) {
    setState(() {
      _value = value;
    });
  },
)

In the CupertinoSlider widget, you can set the divisions property, but it doesn’t have support for label.

The RangeSlider widget has similar properties as the Slider widget: the divisions property is used for displaying the discrete values, and the labels property can be used because there are two labels to be displayed on top of the two slider thumbs. The labels prop takes a variable of type RangeLabels.

After applying the divisions and labels, the Range Slider looks like this:

The basic RangeSlider with the divisions and labels props applied

The code for this slider is given below:

RangeSlider(
  min: 0.0,
  max: 100.0,
  divisions: 10,
  labels: RangeLabels(
    _startValue.round().toString(),
    _endValue.round().toString(),
  ),
  values: RangeValues(_startValue, _endValue),
  onChanged: (values) {
    setState(() {
      _startValue = values.start;
      _endValue = values.end;
    });
  },
)

Displaying a slider’s status

In some situations, you may need to know the current slider status (whether it’s idle, about to be dragged, or is being dragged) in order to update other UI components present on the screen. Fear not — all three types of sliders come with some callbacks that will help you determine the current status of the slider. They are as follows:

  • onChanged: Gets called as the user drags the slider and its value updates
  • onChangeStart: Gets called as the user starts dragging the slider. This callback is typically used to indicate that the user has started dragging and can be used for updating any UI components accordingly
  • onChangeEnd: Gets called as the user stops dragging the slider. This callback is used to indicate that the user has stopped dragging and can be used for updating any UI components accordingly

Of the three callbacks listed above, only onChanged should be used to update the slider value.

Here’s a simple example that uses these callbacks to update a Text widget:

Slider with callbacks

The code used to build this is as follows:

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Slider(
      min: 0.0,
      max: 100.0,
      value: _value,
      divisions: 10,
      onChanged: (value) {
        setState(() {
          _value = value;
          _status = 'active (${_value.round()})';
          _statusColor = Colors.green;
        });
      },
      onChangeStart: (value) {
        setState(() {
          _status = 'start';
          _statusColor = Colors.lightGreen;
        });
      },
      onChangeEnd: (value) {
        setState(() {
          _status = 'end';
          _statusColor = Colors.red;
        });
      },
    ),
    Text(
      'Status: $_status',
      style: TextStyle(color: _statusColor),
    ),
  ],
)

A few variables are initialized inside the class, like this:

double _value = 20;
String _status = 'idle';
Color _statusColor = Colors.amber;

These variable values are updated according to the callbacks and the Text widget is updated by the setState calls.

Applying themes to sliders

Now, we are moving into some deeper customizations that you can apply to the sliders. You can unlock these customizations by wrapping the Slider widget with SliderTheme, which will let you customize each aspect of the slider by specifying various properties.

Let’s build the following slider:

Our example slider with custom themes applied

SliderTheme comes with a ton of properties, but the ones we’ve used to build the above slider are as follows:

The SliderTheme properties that can receive custom color styling

  • trackHeight: Specifies a height for the entire track and applies to both the inactive and the active parts of the track
  • trackShape: Specifies whether the track ends should be rounded and applies to both the inactive and the active parts of the track. Using RoundedRectSliderTrackShape gives it nice rounded edges
  • activeTrackColor: Specifies the color applied to the active part of the track; in the above example, it is the leftmost part, starting from the minimum value up to the slider thumb position
  • inactiveTrackColor: Specifies the color applied on the inactive part of the track; in the above example, it is the rightmost part, starting from the slider thumb position up to the maximum value
  • thumbShape: Specifies a shape to be applied on the slider thumb. The RoundSliderThumbShape gives it a fully rounded thumb, and the radius of the thumb and its press elevation can also be set inside this prop
  • thumbColor: Specifies the color to be applied on the slider thumb
  • overlayColor: Specifies the color to be applied on the overlay that is visible beside the slider thumb when it is in the pressed state; typically, this is a color close to the thumb color but with a lower opacity
  • overlayShape: Specifies the shape of the overlay to be used along with its radius
  • tickMarkShape: Indicates the divisions on the track and specifies a shape to be used to indicate the tick marks on the slider track. These are only visible if the slider has divisions
  • activeTickMarkColor: Specifies the color to be applied to the tick marks present on the active side of the track
  • inactiveTickMarkColor: Specifies the color to be applied to the tick marks present on the inactive side of the track
  • valueIndicatorShape: Specifies the shape of the value indicator, inside which the label (i.e., the value text) is present; this is visible when the slider thumb is in the pressed state
  • valueIndicatorColor: Specifies the color of the value indicator. Typically, a color closer to the slider thumb is used here, but you can specify any color
  • valueIndicatorTextStyle: Specifies the style of the text to be used for displaying the slider value inside the value indicator

The entire code for applying this theme is as follows:

SliderTheme(
  data: SliderTheme.of(context).copyWith(
    trackHeight: 10.0,
    trackShape: RoundedRectSliderTrackShape(),
    activeTrackColor: Colors.purple.shade800,
    inactiveTrackColor: Colors.purple.shade100,
    thumbShape: RoundSliderThumbShape(
      enabledThumbRadius: 14.0,
      pressedElevation: 8.0,
    ),
    thumbColor: Colors.pinkAccent,
    overlayColor: Colors.pink.withOpacity(0.2),
    overlayShape: RoundSliderOverlayShape(overlayRadius: 32.0),
    tickMarkShape: RoundSliderTickMarkShape(),
    activeTickMarkColor: Colors.pinkAccent,
    inactiveTickMarkColor: Colors.white,
    valueIndicatorShape: PaddleSliderValueIndicatorShape(),
    valueIndicatorColor: Colors.black,
    valueIndicatorTextStyle: TextStyle(
      color: Colors.white,
      fontSize: 20.0,
    ),
  ),
  child: Slider(
    min: 0.0,
    max: 100.0,
    value: _value,
    divisions: 10,
    label: '${_value.round()}',
    onChanged: (value) {
      setState(() {
        _value = value;
      });
    },
  ),
)

There are a lot more properties inside SliderTheme that you can play with, though this level of customization should be enough for most users. But you can go even further!

Designing custom sliders with CustomPainter

SliderTheme allows you to modify the slider component shapes only from the pre-existing designs that come with Flutter. If you want to give an out-of-the-box custom look to your slider, CustomPainter) has come to your rescue.

You can create your own designs for the various slider components (like the tick marker, the slider thumb, the slider track, etc.) and assign them as shapes to these components.

We will create a custom shape for the slider thumb and assign it to our Slider widget. It will look like this:

Our custom thumb shape on the Slider widget

For creating this polygon-shaped slider thumb, you’ll first want to define a class to generate this shape that will extend from the SliderComponentShape class:

class PolygonSliderThumb extends SliderComponentShape {
  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    // Define size here
  }

  @override
  void paint(
    PaintingContext context,
    Offset center, {
    required Animation<double> activationAnimation,
    required Animation<double> enableAnimation,
    required bool isDiscrete,
    required TextPainter labelPainter,
    required RenderBox parentBox,
    required SliderThemeData sliderTheme,
    required TextDirection textDirection,
    required double value,
    required double textScaleFactor,
    required Size sizeWithOverflow,
  }) {
    // Define the slider thumb design here
  }
}

While extending the class from SliderComponentShape, you have to define two method overrides:

  1. getPreferredSize(): this method should return the size of the slider thumb shape
  2. paint(): this method generates the design of the slider thumb shape

We need to pass two values to the PolygonSliderThumb class, the thumb radius value and the current selected value of the slider:

class PolygonSliderThumb extends SliderComponentShape {
  final double thumbRadius;
  final double sliderValue;

  const PolygonSliderThumb({
    required this.thumbRadius,
    required this.sliderValue,
  });

  @override
  Size getPreferredSize(bool isEnabled, bool isDiscrete) {
    return Size.fromRadius(thumbRadius);
  }

  // ...
}

Here, we have used the thumbRadius variable to define the radius of the slider thumb shape.

Now, let’s define the shape inside the paint() method. It’s similar to the one we used with CustomPainter and has the same concept:

  • canvas: the drawing area where you apply paint and create your desired shape
  • paint: the brush that you use for drawing

You can access the canvas object from the context passed to the paint() method:

final Canvas canvas = context.canvas;

Define some constants for the number of sides of the polygon, the radius of the inner and outer circles binding the polygons, and finally, calculate the angle according to it:

int sides = 4;
double innerPolygonRadius = thumbRadius * 1.2;
double outerPolygonRadius = thumbRadius * 1.4;
double angle = (math.pi * 2) / sides;

The order of painting operations should be like this:

  1. Outer path
  2. Inner path
  3. Value text

The first one is painted in the beginning, the second one is painted on top of it, followed by the third.

For painting the outer path, the following can be used:

final outerPathColor = Paint()
  ..color = Colors.pink.shade800
  ..style = PaintingStyle.fill;

var outerPath = Path();

Offset startPoint2 = Offset(
  outerPolygonRadius * math.cos(0.0),
  outerPolygonRadius * math.sin(0.0),
);

outerPath.moveTo(
  startPoint2.dx + center.dx,
  startPoint2.dy + center.dy,
);

for (int i = 1; i <= sides; i++) {
  double x = outerPolygonRadius * math.cos(angle * i) + center.dx;
  double y = outerPolygonRadius * math.sin(angle * i) + center.dy;
  outerPath.lineTo(x, y);
}

outerPath.close();
canvas.drawPath(outerPath, outerPathColor);

The inner path can be painted like this:

final innerPathColor = Paint()
  ..color = sliderTheme.thumbColor ?? Colors.black
  ..style = PaintingStyle.fill;

var innerPath = Path();

Offset startPoint = Offset(
  innerPolygonRadius * math.cos(0.0),
  innerPolygonRadius * math.sin(0.0),
);

innerPath.moveTo(
  startPoint.dx + center.dx,
  startPoint.dy + center.dy,
);

for (int i = 1; i <= sides; i++) {
  double x = innerPolygonRadius * math.cos(angle * i) + center.dx;
  double y = innerPolygonRadius * math.sin(angle * i) + center.dy;
  innerPath.lineTo(x, y);
}

innerPath.close();
canvas.drawPath(innerPath, innerPathColor);

Finally, the value text can be painted like this:

TextSpan span = new TextSpan(
  style: new TextStyle(
    fontSize: thumbRadius,
    fontWeight: FontWeight.w700,
    color: Colors.white,
  ),
  text: sliderValue.round().toString(),
);

TextPainter tp = new TextPainter(
  text: span,
  textAlign: TextAlign.center,
  textDirection: TextDirection.ltr,
);

tp.layout();

Offset textCenter = Offset(
  center.dx - (tp.width / 2),
  center.dy - (tp.height / 2),
);

tp.paint(canvas, textCenter);

You can now apply this custom slider thumb shape to the SliderTheme:

SliderTheme(
  data: SliderTheme.of(context).copyWith(
    thumbShape: PolygonSliderThumb(
      thumbRadius: 16.0,
      sliderValue: _value,
    ),
  ),
  child: Slider(...)
)

We won’t go into the building process for any other slider components, but you can use these concepts that we used to build the polygon slider thumb to create a completely customized slider of your own.

Some other amazing slider packages

There are a lot of interesting packages available on pub.dev that provide pre-built sliders with various customization options. Some of them include:

Wrapping up

If you were able to stick with me till now, you deserve a pat on your back! 😉

This article covered all that you need to know in order to master the concept of slider widgets. Now, it’s time for you to dive in and create your own unique-looking slider using Flutter.

Thank you for reading the article! If you have any suggestions or questions about the article or my examples, feel free to connect with me on Twitter or LinkedIn. You can find the code samples of all the sliders that we built in this article on my GitHub repository.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Souvik Biswas Mobile developer (Android, iOS, and Flutter), technical writer, IoT enthusiast, avid video game player.

Leave a Reply