Building A Custom Slider for Flutter

Dhruvam Sharma
Gamezop Tech
Published in
8 min readMar 10, 2020
An image showing Gamezop and Flutter together.

At Gamezop, my role is to build applications for mobile that allows user to play games and win money. Every day a new challenge is discovered while building the application and we try to solve it in the best manner possible. Sometimes it is the User Interface, sometimes it is the architecture and sometimes it is functional. This time, I had the pleasure of building a very customised User Interface that required some maths, some drawing and a lot of thinking.

So this article will revolve around how to build a Custom Slider in Flutter. The UI and UX team gave us a screen that involved this component.

And I was like:

What will we be requiring for this article:

  1. Flutter
  2. Android Studio or VS Code
  3. Internet
  4. 10th Standard Maths

And you wanted to know how the end product will look like? Here it is:

I am going to break down the article into a series of thinking and coding process so that each of us can follow along through the process.

Thinking Process 1:

After looking at the interface, I decided to break down this slider into three parts.

  1. There is a default yellow part on the start of the slider that automatically shows up.
  2. After the user slides it forward, the segmented path shows up.
  3. The last part is the unselected path.

I wanted to start with something simple for firsts.

Coding Process 1:

If you want to head to the code directly, here is the GitHub link. And while you’re at it, give a star :)
If you head over to the GitHub link, you need to fork this project or you can start afresh.

First, check-out the branch slider_setup. In this branch, I created a simple looking slider that comes packed with Flutter. I used the provider package for bubbling up the current value of the slider.

The Custom Slider looks like this.

Without Slider Theme and With Slider Theme.

This is a very simple class that uses some simple colours and text styles and the provider package. I have wrapped the Slider with the SliderTheme to add some colourful changes to the existing Slider. If you were to remove the Slider theme, the results would be the one on the left. No biggie.

Source

Thinking Process 2:

I think we’re pretty much done with the basic layout setup. Now let’s start with the drawing the left end of the slider, the part that the slider starts with. The default width.

Coding Process 2:

After we are done with the basic setup, let’s switch to a new branch, slider_track, only if you’ve forked the repository.

In the SliderTheme in custom_slider.dart file, I decided to add trackShape attribute. Let’s create an empty file and name it custom_track_shape.dart and extend the class with SliderTrackShape Class and override the two methods.

The class looks something like this:

And now if you assign this class to SliderTheme under trackShape attribute, like this:

Then, your app runs something like this:

You can see that there is no slider at all and that’s because we haven’t given anything in the methods of CustomTrackShape class.

Now is the Awesome Drawing part and I need you to take a break, drink water or coffee and BREATHE.

Thinking Process 3:

The first method, getPreferredRect, returns a Rect, that means we need to create a 2D rectangle from the given constraints in where all our drawing will occur.
There are a few lengths I wanted you to know before we start coding:

When the thumb is at the extreme edge of the slider, the total length we need to draw for starts from this extreme left to the extreme right. So the extreme left will be (extreme left of slider + thumb-width/2) because half of the thumb is drawn outside of the slider.

And the same goes for the top edge that we need to start drawing from.

The top left from where we start drawing will be, the (parent’s height - track height)/2

Coding Process 3:

In the method getPreferredRect, we have to return a Rect like this:

return Rect.fromLTWH( left, top, width, height);

If you read the Reading process 3, we have already calculated almost all the lengths required. The rest of the method looks like this:

Here in the code, offset.dx and offset.dy is the amount of padding coming from the parent which we need to take into account so that we don’t draw over them. Now the application looks something like this:

If you do not add the padding parameter (dx and dy), you will get the results on the left (in the above pic) or else if everything is right, you will get the results on the right.

Now if we get to the Paint Method, I want you to think over this:

Some things to notice here:

  1. trackWidth we calculated in the previous method.
  2. maxPlayers is the total number of players (which is 100 in case). This is the max field in the slider.
  3. defaultPlayers is the default number of players that we want to start with.
  4. defaultPlayerWidth is the width in terms of the default players on the slider.
  5. currentPosition is the current value of the slider.
  6. currentPositionWidth is the width in terms of the current position on the slider.

I want you to breathe again before we start with the next segment.

Source

Thinking Process 4:

We are now ready to build the start of the slider track, the one that is the default. No more thinking required.

Coding Process 4:

Let’s select the colour we want for the default part, nothing difficult:

// calculating the paint
// for the default path (initial width)
final Paint defaultPathPaint = Paint()
..color = sliderTheme.activeTrackColor
..style = PaintingStyle.fill;

Let’s start with the default path segment. We know that we need to draw a rectangle and round it’s left edges.

If were to draw something like this, let’s start from scratch.

The defaultPathWidth will be the left part of the slider. As I told you, we will be drawing a rectangle and then rounding its left edge.

Let’s add a rectangle to the path.

final defaultPathSegment = Path()
..addRect(
Rect.fromPoints(
Offset(trackRect.left, trackRect.top),
Offset(
trackRect.left +
(currentPositionWidth >= defaultPlayerWidth
? defaultPlayerWidth
: currentPositionWidth),
trackRect.bottom),
),
);
context.canvas.drawPath(defaultPathSegment, defaultPathPaint);

We added a Rect and with a constructor with two offsets. An offset from top-left to bottom-right. This is how objects are drawn, from top-left to bottom-right. If you can see, I added a condition on the second offset object.

I wanted to draw the left half of the sidebar (the default part) till the current position. If the current position is greater than defaultPlayerWidth, then draw it completely but if not, then draw till the currentPositionWidth.

And this is how it moves:

Let’s round the left-edge:

// calculate the path segment for
// the default width
final defaultPathSegment = Path()
..addRect(
Rect.fromPoints(
Offset(trackRect.left, trackRect.top),
Offset(
trackRect.left +
(currentPositionWidth >= defaultPlayerWidth
? defaultPlayerWidth
: currentPositionWidth),
trackRect.bottom),
),
)
..lineTo(trackRect.left, trackRect.bottom)
..arcTo(
Rect.fromPoints(
Offset(trackRect.left + 5, trackRect.top),
Offset(trackRect.left - 5, trackRect.bottom),
),
-pi * 3 / 2,
pi,
false,
);
context.canvas.drawPath(defaultPathSegment, defaultPathPaint);

I added an arc on the left end with arcTo function. You can play with the angles to see how it goes. I chose 5 as the adding and deleting variable in the Offset because it matched what I needed. What it does is, it increases the radius of the arc.
The result is something like this:

And now we are done with the left end of the Slider.

If we were to follow the same footsteps and draw the right end, it would be pretty similar. I played with angles and it got me the results I wanted.

And the results are like this:

Thinking Process 5:

Everything is almost done and we are now left with is drawing the middle section. The middle section has small white and yellow rectangular boxes spread out over the remaining length.

Coding Process 5:

// Calculation for the selected part of the slider track
// other than the default width
final double selectedPathWidth = currentPositionWidth - defaultPlayerWidth;

I decided to take a small constant (selectedPathBarWidth) that we will be using as the width of the small yellow and white boxes and calculate how many of them will be there inside the remaining length.

// Calculation for the selected part of the slider track
// other than the default width
final double selectedPathWidth = currentPositionWidth - defaultPlayerWidth;

for (int i = 0; i < (selectedPathWidth / selectedPathBarWidth).round();
i++) {
paintCustomSelectedPath(
defaultPlayerWidth,
trackRect,
currentPositionWidth,
context,
i,
sliderTheme,
);
}

I created a new method that calculated the number of boxes and required and drew them on the canvas.

/// This method paints the selected path in our
/// required style
void paintCustomSelectedPath(
double defaultPlayerWidth,
Rect trackRect,
double currentPositionWidth,
PaintingContext context,
int index,
SliderThemeData sliderTheme,
) {
// Selected Path
final Paint borderPaint = Paint()
..color = index % 2 == 0 ? Colors.white : sliderTheme.activeTrackColor
..style = PaintingStyle.fill;

final pathSegmentSelected = Path()
..addRect(
Rect.fromPoints(
Offset(
trackRect.left +
defaultPlayerWidth +
(selectedPathBarWidth * index),
trackRect.top,
),
Offset(
trackRect.left +
defaultPlayerWidth +
(selectedPathBarWidth * index) +
selectedPathBarWidth,
trackRect.bottom,
),
),
);

context.canvas.drawPath(pathSegmentSelected, borderPaint);
}

I created a small path segment and drew it on the canvas until the length of the remaining slider was finished. This last method I created is fairly simple.

And now if you run the application, it looks something like this:

And we are done!

At Gamezop, we build innovative solutions for users to play HTML5 mobile games. A very interesting environment to work in and enjoy. Drop us a hello at careers@gamezop.com to explore :)

#careers #engineering #jobs

--

--

Dhruvam Sharma
Gamezop Tech

Google-certified Android Developer @Unikon. Android Geek. In love with Flutter. Blockchain Explorer. Dancer. 🕺 Reader. Coffee Addict. 😍