Material Neon Progress Bar in Android
Custom view implementation of circular progress bar with android classic material animation and glowing fancy effect
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:
- using only end(sweep) angle animator:
- using both start angle and end angle animators in parallel:
- using only 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 bothcanvas.drawArc()
method’s arguments insideonDraw()
;
Run the code:
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: