📜 ⬆️ ⬇️

Material ProgressBar for pre-Lollipop

At the time of this writing, I am working with great guys at Novoda on an application for broadcasting video for television in the UK Channel 4 . One of the design elements that I had to implement was an endless ProgressBar in the Material Design style. For Android Lollipop and above, the creation of such a design is easy, but now the support of devices of earlier OS versions has become a test for us. In this article we will look at a solution to this problem.

First, let's see how the endless ProgressBar works on Lollipop:


')
While the style of the widget looked pretty easy to implement, the root of the problem lay in animation with an indefinite time. The short line goes from left to right, but its length varies throughout the journey.

First of all, I decided to make a backport to the existing solution. But there were some implementation difficulties, since AnimatedVectorDrawable used there, which is not available until Lollipop. The solution that I found was a little insidious, but it still gave me a surprisingly very similar result.

The solution includes the creation of our own implementation of the ProgressBar (which is the successor of the standard ProgressBar ) which completely bypasses the standard logic of indefinite time and implements its own based on the standard primary and secondary behavior that is already built into the ProgressBar . The trick is in the method of processing it - first the background, then the second progress and then the first. If the background and the primary progress of one color, and the secondary progress of another color, then the effect of changing the length.

Let's look at an example for a better understanding. If we set the background color to light green, the secondary progress is medium green, and the primary progress is dark green, we get the following result:

image

However, if we set the primary color to be the same as the background, we get the desired effect:

image

We can customize the start and end points simply by setting the secondaryProgress value and the ProgressBar value, respectively.

And now let's see how we implemented it in the code:

Implementation code
 public class MaterialProgressBar extends ProgressBar { private static final int INDETERMINATE_MAX = 1000; private static final String SECONDARY_PROGRESS = "secondaryProgress"; private static final String PROGRESS = "progress"; private Animator animator = null; private final int duration; public MaterialProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MaterialProgressBar, defStyleAttr, 0); int backgroundColour; int progressColour; try { backgroundColour = ta.getColor(R.styleable.MaterialProgressBar_backgroundColour, 0); progressColour = ta.getColor(R.styleable.MaterialProgressBar_progressColour, 0); int defaultDuration = context.getResources().getInteger(android.R.integer.config_mediumAnimTime); duration = ta.getInteger(R.styleable.MaterialProgressBar_duration, defaultDuration); } finally { ta.recycle(); } Resources resources = context.getResources(); setProgressDrawable(resources.getDrawable(android.R.drawable.progress_horizontal)); createIndeterminateProgressDrawable(backgroundColour, progressColour); setMax(INDETERMINATE_MAX); super.setIndeterminate(false); this.setIndeterminate(true); } private void createIndeterminateProgressDrawable(@ColorInt int backgroundColour, @ColorInt int progressColour) { LayerDrawable layerDrawable = (LayerDrawable) getProgressDrawable(); if (layerDrawable != null) { layerDrawable.mutate(); layerDrawable.setDrawableByLayerId(android.R.id.background, createShapeDrawable(backgroundColour)); layerDrawable.setDrawableByLayerId(android.R.id.progress, createClipDrawable(backgroundColour)); layerDrawable.setDrawableByLayerId(android.R.id.secondaryProgress, createClipDrawable(progressColour)); } } private Drawable createClipDrawable(@ColorInt int colour) { ShapeDrawable shapeDrawable = createShapeDrawable(colour); return new ClipDrawable(shapeDrawable, Gravity.START, ClipDrawable.HORIZONTAL); } private ShapeDrawable createShapeDrawable(@ColorInt int colour) { ShapeDrawable shapeDrawable = new ShapeDrawable(); setColour(shapeDrawable, colour); return shapeDrawable; } private void setColour(ShapeDrawable drawable, int colour) { Paint paint = drawable.getPaint(); paint.setColor(colour); } . . . } 

The main method here is createIndeterminateProgressDrawable() which replaces the layer in LayerDrawable (which will be processed as a ProgressBar ) with matching colors.

I would also like to note that we rigidly prescribe this as a ProgressBar with an indefinite time in the constructor - in order to preserve the simplicity and ease of understanding the example. In fact, there is additional code for switching the standard ProgressBar to a mode with an indefinite time.

Now we can draw a segment, but how to animate it? This is surprisingly simple - we animate progress and secondary progress with the value of the ProgressBar, but using different interpolators:

Implementation code
 public class MaterialProgressBar extends ProgressBar { . . . @Override public synchronized void setIndeterminate(boolean indeterminate) { if (isStarted()) { return; } animator = createIndeterminateAnimator(); animator.setTarget(this); animator.start(); } private boolean isStarted() { return animator != null && animator.isStarted(); } private Animator createIndeterminateAnimator() { AnimatorSet set = new AnimatorSet(); Animator progressAnimator = getAnimator(SECONDARY_PROGRESS, new DecelerateInterpolator()); Animator secondaryProgressAnimator = getAnimator(PROGRESS, new AccelerateInterpolator()); set.playTogether(progressAnimator, secondaryProgressAnimator); set.setDuration(duration); return set; } @NonNull private ObjectAnimator getAnimator(String propertyName, Interpolator interpolator) { ObjectAnimator progressAnimator = ObjectAnimator.ofInt(this, propertyName, 0, INDETERMINATE_MAX); progressAnimator.setInterpolator(interpolator); progressAnimator.setDuration(duration); progressAnimator.setRepeatMode(ValueAnimator.RESTART); progressAnimator.setRepeatCount(ValueAnimator.INFINITE); return progressAnimator; } } 

Having made our ProgressBar bit more than usual, and slightly slowing down the animation, we will see the following:



Nevertheless, we’ll return it to normal sizes and speeds and compare it to the standard ProgressBar widget of indefinite time implemented in Lollipop:



Of course, they are not identical - the implementation in Lollipop has an animation a second shorter. In spite of this, this implementation is close enough to the original to be used on devices prior to Lollipop.

The source code used in the article is available here .

Source: https://habr.com/ru/post/270317/


All Articles