Fancy Progress Indicators in FlutteršŸŽÆ ā€” without Custom Paint

Paras Jain
8 min readSep 3, 2020

Well, Recently i was working on a Flutter Project where i had to create an Audio Player which shows current Audio Player Position in Waveform.Once i was done creating Audio Waveforms with a bit of Native Code, the next challenge was to create Progress Indicator. While creating that i looked at some current options for reference, and one thing i noticed what that in most cases Custom Painter was being used.

Although Custom Painter is one of the most powerful widgets offered by Flutter but it can introduce unnecessary complexity in some cases. So, in this Tutorial we will be taking a look at alternative way to create Progress Indicators using Shader Mask.

Here are some examples:

So lets get startedā€¦

Now, before moving to Flutter we need to take a quick look at concept called Masking!

What is Masking?

Well, Masking is nothing but the technique of limiting visibility of Media to a more controlled area. Masking can be done in multiple ways but in most basic use cases it requires a Mask. This mask mostly consists of White , Black and Shades of Grey where White Represents Opacity of 1.0, Black represents Opacity of 0.0 and shades of Grey represent all the values in between.

When this Mask is used on , say an Image Fileā€¦ all the areas of image which fall under White color of Mask remain visible, areas under black remain invisible and all the areas under shades of grey are partially visible. A basic example of mask can be seen below:

In Flutter we can achieve this masking effect by using ShaderMask.

Letā€™s Write some Code Now! šŸ‘©ā€šŸ’»

Okay, so now that we know enough about masking we can write some code to get the basic structure of indicator working.

The first thing that we do is create a Simple Container with a fixed width and height and give it a child of Stack. We need a stack here because we need to add 2 child Widgets along Z Axis. First child is going to be a ShaderMask, so lets take a look at how our widget tree will look at this point and then iā€™ll explain to you what is ShaderMask and how it works for our use case:

Shader Mask:

Shader Mask is one of the most impressive widgets offered by Flutter. It primarily requires 2 properties.

  1. shaderCallback
  2. child

shaderCallback takes a function as a value with an argument of Rect instance, this Rect instance is nothing but a fancy representation of total rectangular area that is covered by ShaderMask. This shaderCallback function has to return a shader.

Now to create this shader we can use any of the Gradients given to us by flutter . It really depends upon the use case. In this example we are going to use the SweepGradient. Sweep Gradient (starting at Line 16 in the code above) majorly requires the following properties:

  1. startAngle ā€” Angle in Radians where the Gradient Should Start
  2. endAngle ā€” Angle in Radians where the Gradient should End
  3. alignment
  4. colors ā€” List of colors for Gradient from start to end angle.
  5. stops ā€” List of stops for each color in the List of ā€œcolorsā€

Well, the most important property that we need to concern ourselves with is the ā€œ stops ā€ property.

In the code above you can see that i have commented out Line 19 which has the stops property and the with/without difference can be seen in images below:

The reason why this is a circle, is because of the ā€œchildā€ propertyā€¦ but weā€™ll discuss this soonā€¦šŸ§

In this example we are using 2 Colors for Gradient, which are Blue and Transparent. Initially, in the First Image, when we do not provide any stops to the SweepGradient but Flutter internally sets the value of stops to [ 0.0 , 1.0 ]. In this case the first color of Gradient i.e. Blue, starts at 0.0 and the Last color of Gradient i.e. Transparent ends at 1.0. and thus we see a smooth transition between colors because there are no explicit stopping points provided by us using the stops property.

But when we explicitly provide the stops property (in second image) with a value of [ 0.7 , 0.7 ], here is what it actually means internally:

[0.0 , 0.7 , 0.7 , 1.0 ]

The first and last value of 0.0 and 1.0 are internally provided by Flutter and this translates into Gradient where the White color starts at 0.0 and ends at 0.7 i.e. 70% of the Total Gradient and the Transparent color starts at 0.7 and ends at 1.0 i.e. 100%.

With the help of this stops property we can actually control the percentage of Blue color in our gradient to high precision, which exactly is what progress indicators do!

Now that we have the SweepGradient ready, we can convert it to a shader by simply using the ā€œcreateShaderā€ function of Gradient Object. Take a close look that we are passing the same ā€œrectā€ instance to the ā€œcreateShaderā€ function that we get in the shaderCallback function as an argument . Now all we need to do is to simply return this from Shader Callback and we are good to go.

With all this set, itā€™s time that we take a quick look at that ā€œchildā€ property of ShaderMask because this actually represents the area where the mask will be applied.

You can see that in this child property i am providing a simple Container with a BoxDecoration to change the shape of this Container to circle. Well the most important thing here is to change the color of this Container to White. What actually happens is that the ShaderMask will mask itself to only the white areas of the Container. This is the reason why we get a Circular Look in the first place.

So now that we know how the Shader Mask works and why the child property is necessary, we can take a look at the final output at this stage:

Itā€™s all good but it looks more like a circle than a Ring. Well, itā€™s quite easy to fix actually. All we need to do is to add a second child to the stack which is a Container, circular in shape and white in color. Much like the child of Shader Mask.The only difference here is to change the size of Container to a bit less than the size of ShaderMaskā€™s child.

The use of this container is nothing more than to cover the center area of ShaderMask in Stack and give the whole ui a more Ring like Look. Just for fun i have added a Text Child to this Container where you can actually show some percentage indication. Our code at this point will look something like this:

And now, it creates an output which looks something like this:

Now , we can just animate this for demonstration by wrapping the main Container with a TweenAnimationBuilder (Do not use Tween Animation Builder here for Production, Itā€™s just for Demonstrationā€¦ You probably need to use Animation Controller in such cases). We provide it with a suitable Duration and builder Function. This builder function gives us with 3 arguments which are context, value (of Tween) and the child.

We can use this value of tween to show some percentage in the Text that we added to Second child of Stack. The modified code will look something like this:

At this point we have a basic Circular Progress Indicator ready for us to use in our applications. But wait! ā€¦ this is not Fancy at all. Well, No Worries! All we need to do is to modify the Child of ShaderMask and this in turn will change this Progress Indicator to a much fancy version of itself!

What i am going to do now is add the following image to the assets folder.

Make No Mistake, The Grey color represents transparency hereā€¦

What you need to keep in mind is to check that the image you add is a true PNG and all the visible areas are white in color. Once assets folder is ready and updated in pubspec.yaml, we can use this image as a value for ā€œimageā€ property of BoxDecoration in ShaderMaskā€™s child by using it inside DecoratedImage.

Just with some minor change, we have ourselves a Fancy Progress Indicator which looks something like this:

Using Radial Scale Image

We can change this DecorationImage and have ourself with a completely different design of Progress Indicator like this one:

Using Circular dots Image

Isnā€™t this Awesome!šŸ±ā€šŸ Well, this is the true power of ShaderMask.

Other than Circular Progress Indicators, this very technique can be used to create Audio Waveform Progress, the one i was talking about in the very beginning. For this, we can use an audio waveform image which looks something like this:

And use Linear Gradient instead of SweepGradient. Also we do not need the Second child of Stack and we can change the Colors of Linear Gradient to White and Grey (with an Alpha of 100 or 150).

Along with this we change dimension of the Containers to a more suitable one i.e. 500x100 . The code will look like this:

VoilĆ !!šŸ˜Ž

We now have an Audio Waveform Progress Indicator which looks something like thisā€¦.

It is all possible because of Flutter!, Creating such Complex UIs with such ease!

Github: https://github.com/retroportalstudio/percentage_indicator_sm

Video that corresponds to this article is up on Youtube at:

Link: https://youtu.be/TiH0HYBFMMI

If you find this Useful, Consider Subscribing to RetroPortal Studio on Youtube at:

Here are the links to my other Social Media Handles:

Twitter: https://www.youtube.com/theretroportal

Instagram: https://www.instagram.com/retroportalstudio

LinkedIn: https://www.linkedin.com/in/parasjainrps

Happy Coding! āœŒšŸ˜

--

--

Paras Jain

Mobile Application and Web Developer | @Youtube Content Creator | Worship #reactjs #flutter #java #dart | youtube.com/retroportalstudio