Material Neon Progress Bar in Android

Custom view implementation of circular progress bar with android classic material animation and glowing fancy effect

Yuriy Skul
5 min readJun 23, 2020

This is the second part of Neon UI series.
Part 1 — Neon Progress Bar
Part 3 — in progress

Prehistory

I was excited to play around with implementing neon lighting effect in android regular UI components. Thinking that circular progress bar is going to be the easiest I was surprised when looked into its source code. Looks like its drawable file with xml animation logic is a part of AOSP and can’t be simply resolved.
Have found nice series with good explanation of animation behavior of android post lollipop circular indeterminate progress bar written by Mark Allison:

https://blog.stylingandroid.com/indeterminate-part-1/
https://blog.stylingandroid.com/indeterminate-part-2
https://blog.stylingandroid.com/indeterminate-part-3/
https://blog.stylingandroid.com/indeterminate-part-4/

So I am not going to go into deep explanation, exploring and analyzing. Going just apply material progress bar animation to neon simple progress bar from my previous article in accordance with those articles.

1. Starter code

It could be done in different ways. Using ImageView with custom drawable + specific animator set or in a proper way using public progressBar API methods such as setIndeterminateDrawable(…) and setInterpolator(…) so result would be production level. But in this article I am going just to create simple solution by extending View class. This is definitely not a production solution. Just the basic idea…

Colors for body progress bar arc and for glowing arc retrospectively.

And Java class extended from android.view.View:

In article pictures I use xml view 100X100 dp so assigning to the body arc width 12 dp looks fine with current size. But in case of smaller sizes it should have width value lesser than 12 dp.

Let body arc width be 12 dp.
And let define that glowing arc width is greater than body width 1.5 times.
And blurring radius is going to be 2 times greater than body arc width.

Add these constants to the current class:

Define next fields:

Inside init() method disable hardware acceleration because we are going to use BlurMaskFilter:

Update init() with next:

And define convertDpToPixel(…) method in this class:

Now we can define and initialize local variables using bodyStrokeWidthPx and defined constants proportion multipliers inside init() method:

So mPaddingPx has to be glowStrokeWidthPx / 2 + blurRadiusPx;
mPaddingPx = glowStrokeWidthPx / 2 + blurRadiusPx; mPaddingPx is an instance variable because we are going to use it in another place of another method:

Initialize next variables in init() method:

And define createBodyPaint() and createGlowingPaint() methods inside this class:

Next override onSizeChanged(…) method:

Define fields for drawing arc:
(Start angle is 0 by default and end initialize with 270 degrees:

And finally override onDraw(…) method:

Set the parent layout background to the black in activity layout xml for better appearance:
android:background=”@android:color/black”

Run the project and expected result is:

2. Create AnimatorSet

The idea is to run three animators in parallel and each of them defines start angle position, end angle position and common rotation of the whole drawing.

To demonstrate animator’s impact I am going to run solution code using only one of it and with addition red dot marker for better visualization.

  • using only start angle animator, red marker dot is associated to the start:
Start angle animator’s impact
Result of using only start angle animator in solution code
  • using only end(sweep) angle animator:
Sweep animator impact
Result of using only sweep animator in solution code
  • using both start angle and end angle animators in parallel:
Result of using start and sweep angle animators in parallel
  • using only rotation animator:
Result of simple rotation animator

Let’s put the logic of generating AnimatorSet of those three animators in a separate class. This class is just a holder of static creation methods.
And define next constants for animation in this class:

Define public creation method which returns the AnimationSet of those three animators: startAngleAnimator, sweepAngleAnimator and rotationAnimator:

And three private methods responsible for generation these animators:

  • StartAngleAnimator:
  • SweepAngleAnimator:
  • RotationAnimator:

We could define directly instance of MaterialNeonProgressBar as method’s argument :

private static Animator createStartAngleAnimator(MaterialNeonProgressBar target){

or like upcasting to the Object:

@SuppressLint("ObjectAnimatorBinding")
private static Animator createStartAngleAnimator(Object target) {

But for specifying contract it is done with helper Interface as an argument of every method of AnimatorCreator class.

So let’s define this interface as a nested one inside AnimatorCreator class because it is related to this class:

Define inside AnimatorTargetInterface property names as constants:

2. Apply Animation

Switch back to the MaterialNeonProgressBar class:
- make it implement AnimatorTargetInterface;
- add new field mRotation below mEndAngle variable definition;
- implement AnimatorTargetInterface’s methods;

Next, add:
- delegate method which returns an instance of Animator we going to use;
- and field to hold this instance;

Instantiate mAnimator reference int the init() method:

Running animation can be done by calling mAnimator.start() and can be stopped/paused if needed(view goes invisible or container fragment slides away from the screen)

  • let’s start animation from onDraw() method;
    - modify both canvas.drawArc() method’s arguments inside onDraw();

Run the code:

Weird animation caused by using wrong interpolators

Well, it still looks unlike android material…

AOSP xml animation for indeterminate material progress bar has trim_start_interpolator and trim_end_interpolator.
It creates PathInterpolator instances from path data:

Revealing the mystery of PathInterpolator mechanic of animation material android indeterminate progress bar in depth could be found in Mark Alisson series of articles.

Inside AnimatorCreator class define two methods:

and modify inside methods createStartAngleAnimator() and createSweepAngleAnimator() next lines by passing PathInterpolator’s instances instead of DecelerateInterpolator and AccelerateInterpolator:

That’s all. Thanx for reading :)

Solution code:

--

--