Flutter: Sliders DeMystified

Ankit Chowdhury
Flutter Community
Published in
10 min readJan 14, 2020

Value Indicator Thumb, Custom Tracks, Overlays and more

Updated : 8/11/2020. Flutter Stable 1.20.1, Dart 2.9.0

So after spending a good 7 years with Native Android Development, having dealt with both the Android SDK & NDK , amassing more than 3 Million downloads on Google Play in the process, Flutter came to me, albeit a little late. While attending a Google For Mobile Event in November 2019, and so impressed was I, that I stalled the then current project of mine, picked another from my pipeline of projects in order to Test Drive Flutter. Flutter & Dart have since felt like a breath of fresh air. 😊

Time to Slide back to our Agenda.

Sliders in Flutter

design rights : twitter.com/dexterxo_

Flutter, in general, has been extremely benevolent as far as providing customization options to UI Components are concerned. Sliders are no exception to this rule. 80% of the Custom Slider above can be achieved using the built-in styling options. 20% requires Custom Painting.

In this Article, we’ll discuss about the Styling options available natively along with how we can draw components of the Slider from Scratch.

Starting with the Basics

A Slider in Flutter, devoid of any kind of custom styling, in it’s most rudimentary form, looks like this. Check out the code below.

But by wrapping it with a SliderTheme and by supplying a SliderThemeData we open up a plethora of Theming options. SliderThemeData is where the actual magic happens.

As is evident, wrapping the Slider with a SliderTheme gives us plenty more parameters to tinker with. We oblige and Voila! We get this.

But we’re just warming up. The Slider above still looks quite basic and we want to create something intricate. In order to add intricacies to the Widget, we need to understand it’s components first. Let’s have a quick look.

Breaking the Slider Widget into it’s Components

The primary components are as follows:

  • the thumb : is the shape that slides horizontally when the user drags it.
  • the track : is the line that the slider thumb slides along. It has an active and an inactive side.
  • the overlay : is the halo effect that appears while the thumb is pressed while dragging.
  • the tick marks : are regularly spaced marks that are drawn when using discrete divisions.
  • the value indicator : appears when the user is dragging the thumb to indicate the value being selected

These components correspond directly to the SliderTheme code snippet presented earlier in the article. I’m adding a slightly modified version below as well, for reference.

SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: Colors.red[700],
inactiveTrackColor: Colors.red[100],
trackShape: RoundedRectSliderTrackShape(),
trackHeight: 4.0,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 12.0),
thumbColor: Colors.redAccent,
overlayColor: Colors.red.withAlpha(32),
overlayShape: RoundSliderOverlayShape(overlayRadius: 28.0),
tickMarkShape: RoundSliderTickMarkShape(),
activeTickMarkColor: Colors.red[700],
inactiveTickMarkColor: Colors.red[100],
valueIndicatorShape: PaddleSliderValueIndicatorShape(),
valueIndicatorColor: Colors.redAccent,
valueIndicatorTextStyle: TextStyle(
color: Colors.white,
),
),
child: Slider(
value: _value,
min: 0,
max: 100,
divisions: 10,
label: '$_value',
onChanged: (value) {
setState(
() {
_value = value;
},
);
},
),
),
The Slider corresponding to the above code.

It’s not particularly difficult to notice, that for every component we discussed above, the SliderThemeData object takes a certain kind of Shape. It also takes in parameters like activeTrackColor, overlayColor, trackHeight, etc. which help us customize one or the other aspect of the Components. Although self-explanatory, while we are at it, let’s understand some of these properties better.

The Properties explained

  • activeTrackColor : activeTrack is generally the side of the track from min to the thumb. We ought to assign a Color object to this property.
  • inactiveTrackColor : inactiveTrack is generally the side of the track from the thumb to max. We assign a Color object to this property.
  • trackShape : takes in the shape of the track on which the Slider thumb slides. RoundedRectSliderTrackShape is assigned implicitly to it. We’ll discuss about how this shape can be customised later in the article.
  • trackHeight : defines the height of the track in pixels. Takes in a value of type double.
  • thumbShape : takes in the shape of the thumb. RoundSliderThumbShape is assigned implicitly to it. We can change the size of the thumb by passing the radius value into this. We will customize this Shape to achieve what to started off to do.
  • overlayShape : shape of the halo effect behind the thumb. RoundSliderOverlayShape is assigned implicitly to it. The value of radius can be passed to the shape.
  • tickMarkShape : tick marks are shown with discrete value selections in Sliders. These marks are again Shapes which can be customised. RoundSliderTickMarkShape is the implicit assignment. Again radius value can be passed to it.
  • valueIndicatorShape : valueIndicator appears when the user is dragging the thumb to indicate the value being selected. This helps set the shape where the value is shown. PaddleSliderValueIndicatorShape is the default shape of a Slider’s value indicator.
  • valueIndicatorTextStyle : This parameter helps set a TextStyle to the Text appearing on the Indicator.

This is not an exhaustive list of all the properties of the SliderThemeData class. The ones mentioned, cover most use cases. Most of the other properties are used to handle the disabled state of the widget.

Everything Shapes Pro

credits : material.io

Almost every element of an User Interface is or contains a shape. Shapes are primitive in nature. A Shape can be a Circle, Rectangle, a Rounded Rectangle or even a Line or an Arc. Tons of UI components can be built using these Primitive Shapes.

Thankfully though, Flutter engineers have done the hard bit and have provided us with the more Sophisticated UI components by binding these shapes with logic via the so called Widgets. These sophisticated Widgets have helped Devs all over the World create much more complex User Interface instead of trying to reinvent the wheel ever so often. Precisely why, Flutter is gaining such traction amongst developers.

But, Flutter is still in it’s infancy. It’s solid already. But it’s new. And every now and then you’ll find yourself looking for a feature which is missing in a Widget. But Flutter has got you covered again. With it’s custom painting abilities you can always create that missing feature or widget seamlessly.

CustomPaint might seem daunting at first, but once you get the hang of it, it’ll prove to be incredibly rewarding. This article is probably the best source of info as far as custom painting in Flutter is concerned. It has everything and more. Kudos to Muhammed Salih Guler.

By now, it’s not difficult to guess how custom painting is relevant to this article. So let’s dive into it.

The Shapes in Flutter’s Slider

The thumb, the track, the overlay, the tick marks, all of these components are nothing but shapes. The default thumbShape for a Material Slider is RoundSliderThumbShape. This is the Class which is implicitly used to build the thumb.

On checking the Source code of this Class, it becomes obvious that the Class uses the Canvas to draw the shape. Meaning we can pretty much draw any custom shape, text, etc. It falls into the custom painting domain of Flutter. Only slightly dissimilar. This class extends the SliderComponentShape class.

class RoundSliderThumbShape extends SliderComponentShape {
const RoundSliderThumbShape({
this.enabledThumbRadius = 10.0,
this.disabledThumbRadius,
});

final double enabledThumbRadius;

final double disabledThumbRadius;
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;

@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
}

@override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
assert(context != null);
assert(center != null);
assert(enableAnimation != null);
assert(sliderTheme != null);
assert(sliderTheme.disabledThumbColor != null);
assert(sliderTheme.thumbColor != null);

final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: _disabledThumbRadius,
end: enabledThumbRadius,
);
final ColorTween colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
canvas.drawCircle(
center,
radiusTween.evaluate(enableAnimation),
Paint()..color = colorTween.evaluate(enableAnimation),
);
}
}

The points of interest here are the 2 overridden methods. The getPreferredSize and paint methods.

The paint method is used directly for the custom drawing. Since we are extending the SliderComponentShape class, the paint method receives all the relevant data required for building of the thumb, overlay or any other component which extends the SliderComponentShape class to build.

Not gonna waste any more time explaining individual properties because example is always better than precept. So let’s learn through actual production code by creating the example we started off with.

The Real Deal

Slider with Continuous Values
Slider with Discrete Divisions.
Slider with Rectangular Thumb

These are the kind of Sliders we wanted to create since the very beginning. But what’s special about them compared to the basic ones. A few things :-

  • the thumb in itself includes a value indicator which is visible at all times, unlike the built-in value indicator which pops up only while the thumb is being dragged or pressed down.
  • has thumbs of shapes other than circular. The example shows rounded rectangular shaped thumb.
  • the track is invisible when working with Discrete values. Revealing only the tick marks which has active and inactive sides with separate colors. This is done by setting opacity of both active and inactive track colors to zero.
  • The slider is housed inside a rectangle with rounded edges and gradient background. This is done by wrapping the slider inside a Container with borderRadius and LinearGradient.

The Value Indicator Thumb

The properties of our Custom Thumb Class includes the thumbRadius, and the min and max values of the slider which are required to output the value on the thumb.

Custom Thumb Drawing Pre-requisites

We know the actual custom painting takes place inside the paint() . Hence it also supplies us with all the relevant data required for the drawing of the components. Some of the important data are :

  • context : we extract the canvas using the context.
  • center : helps us align our component shape.
  • isDiscrete : boolean value suggesting whether slider uses divisions.
  • sliderTheme : a reference to the SliderThemeData object we wrapped the slider with. We can extract crucial theme data from it.
  • value : gives the value of the slider normalized to a range from 0.0 to 1.0. We can easily change the origin according to our range.

What are the tools we use to draw then?

  • canvas : is our drawing board.
  • paint : is our brush we draw with. we can use fill, stroke, etc.
  • TextSpan & TextPainter : are used to print text on our shapes. Can be used to style our text too.

For more info regarding custom drawing please refer to this article.

Some key points in the code above that probably needs explanation :

  • the paint object is created to fill color into the shape. We can also use it add stroke,etc. Read the article above for more info.
  • we convert the normalized value from paint() into our range using the method called getValue() .
  • there was a need to find an offset to draw the text at the absolute center. using the center offset provided by the paint() was never gonna work as the text painter aligns it’s own top-left with the offset supplied. Hence the extra code to figure out the correct textCenter offset. Here’s how it looked when center offset from paint() was used as text offset.
  • then the canvas.draw functions are used to draw the shapes.
  • the text is drawn after that using the textPainter object. Pretty straightforward.

Here’s the code for Rounded Rectangular Thumb

Other Components

For the sake of brevity, we cannot create Custom forms of all the Component shapes. But I can safely assume that this article has provided enough info for you to be able to customize every tiny bit of the Material Slider Widget.

I’m attaching the code of the Slider itself below. It’s fairly general in nature but might contain some slightly bespoke code.

Will discuss about my previous and upcoming projects some time soon. Will share my experience from the past decade and how I’m looking to evolve in this one.

Hope this article helps you in some form or the other. Clap your hands👏 if it does. Clap 👏 +50 times if you love this article.

Play around with the code. Create beautiful sliders. DM me whatever you create. Always eager to hear from creative devs.

Do let me know if you find something to be incorrect. There’s always room for improvement.

Follow me on Twitter too : @ankitc_

See ya. 👋

--

--

Ankit Chowdhury
Flutter Community

3 Million+ Downloads on Google Play, Founder : Mantis Pro Gaming, Veteran @ XDA-Developers. dev’ing professionally since 2013