25

Background

Android has a standard ProgressBar with a special animation when being indeterminate . There are also plenty of libraries of so many kinds of progress views that are available (here).

The problem

In all that I've searched, I can't find a way to do a very simple thing:

Have a gradient from color X to color Y, that shows horizontally, and moves in X coordinate so that the colors before X will go to color Y.

For example (just an illustration) , if I have a gradient of blue<->red , from edge to edge , it would go as such:

enter image description here

What I've tried

I've tried some solutions offered here on StackOverflow:

but sadly they all are about the standard ProgressBar view of Android, which means it has a different way of showing the animation of the drawable.

I've also tried finding something similar on Android Arsenal website, but even though there are many nice ones, I couldn't find such a thing.

Of course, I could just animate 2 views myself, each has a gradient of its own (one opposite of the other), but I'm sure there is a better way.

The question

Is is possible to use a Drawable or an animation of it, that makes a gradient (or anything else) move this way (repeating of course)?

Maybe just extend from ImageView and animate the drawable there?

Is it also possible to set how much of the container will be used for the repeating drawable ? I mean, in the above example, it could be from blue to red, so that the blue will be on the edges, and the red color would be in the middle .


EDIT:

OK, I've made a bit of a progress, but I'm not sure if the movement is ok, and I think that it won't be consistent in speed as it should, in case the CPU is a bit busy, because it doesn't consider frame drops. What I did is to draw 2 GradientDrawables one next to another, as such:

class HorizontalProgressView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private val speedInPercentage = 1.5f
    private var xMovement: Float = 0.0f
    private val rightDrawable: GradientDrawable = GradientDrawable()
    private val leftDrawable: GradientDrawable = GradientDrawable()

    init {
        if (isInEditMode)
            setGradientColors(intArrayOf(Color.RED, Color.BLUE))
        rightDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
        rightDrawable.orientation = GradientDrawable.Orientation.LEFT_RIGHT
        rightDrawable.shape = GradientDrawable.RECTANGLE;
        leftDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT;
        leftDrawable.orientation = GradientDrawable.Orientation.RIGHT_LEFT
        leftDrawable.shape = GradientDrawable.RECTANGLE;
    }

    fun setGradientColors(colors: IntArray) {
        rightDrawable.colors = colors
        leftDrawable.colors = colors
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
        rightDrawable.setBounds(0, 0, widthSize, heightSize)
        leftDrawable.setBounds(0, 0, widthSize, heightSize)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.save()
        if (xMovement < width) {
            canvas.translate(xMovement, 0.0f)
            rightDrawable.draw(canvas)
            canvas.translate(-width.toFloat(), 0.0f)
            leftDrawable.draw(canvas)
        } else {
            //now the left one is actually on the right
            canvas.translate(xMovement - width, 0.0f)
            leftDrawable.draw(canvas)
            canvas.translate(-width.toFloat(), 0.0f)
            rightDrawable.draw(canvas)
        }
        canvas.restore()
        xMovement += speedInPercentage * width / 100.0f
        if (isInEditMode)
            return
        if (xMovement >= width * 2.0f)
            xMovement = 0.0f
        invalidate()
    }
}

usage:

    horizontalProgressView.setGradientColors(intArrayOf(Color.RED, Color.BLUE))

And the result (it does loop well, just hard to edit the video) :

enter image description here

So my question now is, what should I do to make sure it animates well, even if the UI thread is a bit busy ?

It's just that the invalidate doesn't seem a reliable way to me to do it, alone. I think it should check more than that. Maybe it could use some animation API instead, with interpolator .

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • What about an animated GIF? or a series of PNG frames (works on the same principle, but you can have more colors and transparency levels). The latter is how Android animation (frame animation) works. Give your progessbar (but at this level, it could even be a TextView) a background color and you're done. – Phantômaxx Jan 30 '18 at 12:18
  • 1
    @KlingKlang This is not a recommended solution, as both are known to be bad in performance and memory usage compared to true animation. – android developer Jan 30 '18 at 13:22
  • "true animation" is actually a sequence of frames. Since the Lumiere brothers to our times. – Phantômaxx Jan 30 '18 at 13:45
  • @KlingKlang OP is right: the series of PNG will eat the precious memory - in fact all you have to do is to draw x axis translated gradient inside `Drawable#draw` method - this is true animation done the right way – pskink Jan 30 '18 at 14:01
  • @pskink But this would consume cpu time for calculations. – Phantômaxx Jan 30 '18 at 14:03
  • @KlingKlang `Canvas#drawPaint` will consume more cpu than `Canvas#drawBitmap`? not at all – pskink Jan 30 '18 at 14:04
  • @KlingKlang Maybe, but would perform better and with less RAM. Also have perfect quality, no matter the pixel density of the device – android developer Jan 30 '18 at 14:04
  • @androiddeveloper screen density is a good point. – Phantômaxx Jan 30 '18 at 14:05
  • OK I think I did it (updated question with current code), but I think the movement might not be smooth on some cases, as it doesn't take into considerations the fluctuations of the UI thread usage. If any of you could help, it would be great. – android developer Feb 01 '18 at 09:54
  • how to do this for button in android – Vivek Jul 31 '22 at 11:59
  • @Vivek Either try to have it as a background for it, or have it as a layer behind the button and have a different background for the button, or anything else... – android developer Jul 31 '22 at 12:03
  • i have already done that but unable to animate. – Vivek Aug 01 '22 at 19:26
  • @Vivek Oh I think it was meant to be used for ProgressView . Not what I just wrote. Try it, with indeterminate set to true. – android developer Aug 01 '22 at 22:47

6 Answers6

5

I've decided to put " pskink" answer here in Kotlin (origin here). I write it here only because the other solutions either didn't work, or were workarounds instead of what I asked about.

class ScrollingGradient(private val pixelsPerSecond: Float) : Drawable(), Animatable, TimeAnimator.TimeListener {
    private val paint = Paint()
    private var x: Float = 0.toFloat()
    private val animator = TimeAnimator()

    init {
        animator.setTimeListener(this)
    }

    override fun onBoundsChange(bounds: Rect) {
        paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.WHITE, Color.BLUE, Shader.TileMode.MIRROR)
    }

    override fun draw(canvas: Canvas) {
        canvas.clipRect(bounds)
        canvas.translate(x, 0f)
        canvas.drawPaint(paint)
    }

    override fun setAlpha(alpha: Int) {}

    override fun setColorFilter(colorFilter: ColorFilter?) {}

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

    override fun start() {
        animator.start()
    }

    override fun stop() {
        animator.cancel()
    }

    override fun isRunning(): Boolean = animator.isRunning

    override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
        x = pixelsPerSecond * totalTime / 1000
        invalidateSelf()
    }
}

usage:

MainActivity.kt

    val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.getDisplayMetrics())
    progress.indeterminateDrawable = ScrollingGradient(px)

activity_main.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"
    tools:context=".MainActivity">

    <ProgressBar
        android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="200dp"
        android:layout_height="20dp" android:indeterminate="true"/>
</LinearLayout>
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • I'm curious how the code from this answer differs from the one from [Cheticamp's answer](https://stackoverflow.com/a/48678279/1083957). – azizbekian Feb 12 '18 at 06:25
  • @azizbekian Don't know. He has added the new code after what I wrote. You'd better ask him instead of me. – android developer Feb 12 '18 at 15:43
  • @azizbekian The short answer is that I didn't write that; pskink did with a Kotlin assist from the OP (?) My original code has somehow disappeared from my own post. You can look at the edit to see what I had written, although what is currently posted appears to be superior. I have since posted new code that is an improvement on what I had posted originally. – Cheticamp Feb 12 '18 at 21:08
4

The idea behind my solution is relatively simple: display a FrameLayout that has two child views (a start-end gradient and a end-start gradient) and use a ValueAnimator to animate the child views' translationX attribute. Because you're not doing any custom drawing, and because you're using the framework-provided animation utilities, you shouldn't have to worry about animation performance.

I created a custom FrameLayout subclass to manage all this for you. All you have to do is add an instance of the view to your layout, like this:

<com.example.MyHorizontalProgress
    android:layout_width="match_parent"
    android:layout_height="6dp"
    app:animationDuration="2000"
    app:gradientStartColor="#000"
    app:gradientEndColor="#fff"/>

You can customize the gradient colors and the speed of the animation directly from XML.

The code

First we need to define our custom attributes in res/values/attrs.xml:

<declare-styleable name="MyHorizontalProgress">
    <attr name="animationDuration" format="integer"/>
    <attr name="gradientStartColor" format="color"/>
    <attr name="gradientEndColor" format="color"/>
</declare-styleable>

And we have a layout resource file to inflate our two animated views:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <View
        android:id="@+id/one"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <View
        android:id="@+id/two"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</merge>

And here's the Java:

public class MyHorizontalProgress extends FrameLayout {

    private static final int DEFAULT_ANIMATION_DURATION = 2000;
    private static final int DEFAULT_START_COLOR = Color.RED;
    private static final int DEFAULT_END_COLOR = Color.BLUE;

    private final View one;
    private final View two;

    private int animationDuration;
    private int startColor;
    private int endColor;

    private int laidOutWidth;

    public MyHorizontalProgress(Context context, AttributeSet attrs) {
        super(context, attrs);

        inflate(context, R.layout.my_horizontal_progress, this);
        readAttributes(attrs);

        one = findViewById(R.id.one);
        two = findViewById(R.id.two);

        ViewCompat.setBackground(one, new GradientDrawable(LEFT_RIGHT, new int[]{ startColor, endColor }));
        ViewCompat.setBackground(two, new GradientDrawable(LEFT_RIGHT, new int[]{ endColor, startColor }));

        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            @Override
            public void onGlobalLayout() {
                laidOutWidth = MyHorizontalProgress.this.getWidth();

                ValueAnimator animator = ValueAnimator.ofInt(0, 2 * laidOutWidth);
                animator.setInterpolator(new LinearInterpolator());
                animator.setRepeatCount(ValueAnimator.INFINITE);
                animator.setRepeatMode(ValueAnimator.RESTART);
                animator.setDuration(animationDuration);
                animator.addUpdateListener(updateListener);
                animator.start();

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
                else {
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            }
        });
    }

    private void readAttributes(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MyHorizontalProgress);
        animationDuration = a.getInt(R.styleable.MyHorizontalProgress_animationDuration, DEFAULT_ANIMATION_DURATION);
        startColor = a.getColor(R.styleable.MyHorizontalProgress_gradientStartColor, DEFAULT_START_COLOR);
        endColor = a.getColor(R.styleable.MyHorizontalProgress_gradientEndColor, DEFAULT_END_COLOR);
        a.recycle();
    }

    private ValueAnimator.AnimatorUpdateListener updateListener = new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            int offset = (int) valueAnimator.getAnimatedValue();
            one.setTranslationX(calculateOneTranslationX(laidOutWidth, offset));
            two.setTranslationX(calculateTwoTranslationX(laidOutWidth, offset));
        }
    };

    private int calculateOneTranslationX(int width, int offset) {
        return (-1 * width) + offset;
    }

    private int calculateTwoTranslationX(int width, int offset) {
        if (offset <= width) {
            return offset;
        }
        else {
            return (-2 * width) + offset;
        }
    }
}

How the Java works is pretty simple. Here's a step-by-step of what's going on:

  • Inflate our layout resource, adding our two to-be-animated children into the FrameLayout
  • Read the animation duration and color values from the AttributeSet
  • Find the one and two child views (not very creative names, I know)
  • Create a GradientDrawable for each child view and apply it as the background
  • Use an OnGlobalLayoutListener to set up our animation

The use of the OnGlobalLayoutListener makes sure we get a real value for the width of the progress bar, and makes sure we don't start animating until we're laid out.

The animation is pretty simple as well. We set up an infinitely-repeating ValueAnimator that emits values between 0 and 2 * width. On each "update" event, our updateListener calls setTranslationX() on our child views with a value computed from the emitted "update" value.

And that's it! Let me know if any of the above was unclear and I'll be happy to help.

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • I already wrote that I prefer not to use a 2 views solution. Nice solution though – android developer Feb 03 '18 at 07:31
  • 1
    `ProgressBar` has an attribute `android:indeterminateDrawable` which is used for a presentation in "indeterminate" mode - the easiest solution is to create a custom `Drawable` class that implements `Animatable` and start a `TimeAnimator` inside `Animatable#start` method – pskink Feb 04 '18 at 15:46
  • and the usage is as simple as this: `ProgressBar pb = ...; pb.setIndeterminate(true); pb.setIndeterminateDrawable(new ScrollingGradient());` – pskink Feb 04 '18 at 18:16
  • @pskink Yes I thought it might be possible, but I'm not sure what's the best way to do it in Drawable for the ProgressBar. Can you please show how to create this drawable, in code? – android developer Feb 06 '18 at 12:02
  • 1
    @androiddeveloper ok since i really cannot believe seeing those hard workarounds to make such simple thing like showing a standard `ProgressBar` in "indeterminate" mode, here is that custom `Drawable`: https://pastebin.com/raw/dnAMwSd1 – pskink Feb 08 '18 at 16:52
  • @pskink Why not write it in an answer? Seems to work well. What would it take to make the progressing in percentage like I did though? For some reason, it seems slower than what I'd expect: I tried setting the width to be 200dp and the speed to be 200dp per second, but it doesn't look like it does finish it in a second, meaning the white area doesn't reach from one edge to the other – android developer Feb 08 '18 at 22:45
  • @pskink Anyway, I've put your answer below: https://stackoverflow.com/a/48696216/878126 . If you put it as your own, I will accept it and provide the bounty – android developer Feb 08 '18 at 23:26
  • @pskink Is there perhaps a different way to set the speed? – android developer Mar 19 '18 at 09:36
  • yes, you can pass a fraction of view's width, for example `1.42f` – pskink Mar 19 '18 at 09:46
0
final View bar = view.findViewById(R.id.progress);
final GradientDrawable background = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, new int[]{Color.BLUE, Color.RED, Color.BLUE, Color.RED});
bar.setBackground(background);
bar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        background.setBounds(-2 * v.getWidth(), 0, v.getWidth(), v.getHeight());
        ValueAnimator animation = ValueAnimator.ofInt(0, 2 * v.getWidth());
        animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                background.setBounds(-2 * v.getWidth() + (int) animation.getAnimatedValue(), 0, v.getWidth() + (int) animation.getAnimatedValue(), v.getHeight());
            }
        });
        animation.setRepeatMode(ValueAnimator.RESTART);
        animation.setInterpolator(new LinearInterpolator());
        animation.setRepeatCount(ValueAnimator.INFINITE);
        animation.setDuration(3000);
        animation.start();
    }
});

This is the view for testing:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center" >

    <View
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>

</FrameLayout>
artkoenig
  • 7,117
  • 2
  • 40
  • 61
  • Tried to use this code, but nothing appears on the view. Please show full code – android developer Feb 08 '18 at 21:50
  • Odd. Was sure I did such a thing. Now it works fine. – android developer Feb 09 '18 at 21:54
  • Sadly the bounty was auto-granted, so all I can do is give you +1 . Is it efficient though, to continually call `setBounds` over and over? – android developer Feb 09 '18 at 21:58
  • Think so, `setBounds` just invalidates the `Drawable` and cause the redraw. But pskink's solution is much cleaner and should be accepted as the right answer. – artkoenig Feb 11 '18 at 09:42
  • The `setBounds` reminds me of something a respected Android developer wrote to me about Drawable : "You should also not be changing the bounds during draw." : https://github.com/android/android-ktx/issues/210#issuecomment-363435857 . Since here it is called a lot, I think it's a similar thing. Sadly he didn't explain more, and didn't tell me how things should be. – android developer Feb 11 '18 at 12:24
  • `"setBounds just invalidates the Drawable and cause the redraw."` - for `GradientDrawable` it makes some other things: each time you call `setBounds` a new `LinearGradient` will be created - setup a brealpoint [here](https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/drawable/GradientDrawable.java#1140) and you will see that it goes there each time `onAnimationUpdate` is called – pskink Feb 11 '18 at 13:34
0

You may achieve it if you have different drawables which defines the colors which are required to show as progress bar.

Use AnimationDrawable animation_list

 <animation-list android:id="@+id/selected" android:oneshot="false">
    <item android:drawable="@drawable/color1" android:duration="50" />
    <item android:drawable="@drawable/color2" android:duration="50" />
    <item android:drawable="@drawable/color3" android:duration="50" />
    <item android:drawable="@drawable/color4" android:duration="50" />
    -----
    -----
 </animation-list>

And in your Activity/xml set this as a background resource to your progressbar.

Then do as follows

// Get the background, which has been compiled to an AnimationDrawable object.
 AnimationDrawable frameAnimation = (AnimationDrawable)prgressBar.getBackground();

 // Start the animation (looped playback by default).
 frameAnimation.start();

If we take the respective drawables in such a way which covers blue to red and red to blue gradient effects respectively those images we have to mention in animation list as color1, color2 etc

This approach is similar to how we will make a GIF image with multiple static images.

ppreetikaa
  • 1,149
  • 2
  • 15
  • 22
0

I've modified 'android developer's code slightly which might help some people.

The animation didn't seem to resize properly so I've fixed that, made the animation speed a bit easier to set (seconds instead of pixel based) and relocated the init code to allow embedding right into the layout xml without code in your Activity.

ScrollingProgressBar.kt

package com.test

import android.content.Context
import android.util.AttributeSet
import android.widget.ProgressBar
import android.animation.TimeAnimator
import android.graphics.*
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable

class ScrollingGradient : Drawable(), Animatable, TimeAnimator.TimeListener {

    private val paint = Paint()
    private var x: Float = 0.toFloat()
    private val animator = TimeAnimator()
    private var pixelsPerSecond: Float = 0f
    private val animationTime: Int = 2

    init {
        animator.setTimeListener(this)
    }

    override fun onBoundsChange(bounds: Rect) {
        paint.shader = LinearGradient(0f, 0f, bounds.width().toFloat(), 0f, Color.parseColor("#00D3D3D3"), Color.parseColor("#CCD3D3D3"), Shader.TileMode.MIRROR)
        pixelsPerSecond = ((bounds.right - bounds.left) / animationTime).toFloat()
    }

    override fun draw(canvas: Canvas) {
        canvas.clipRect(bounds)
        canvas.translate(x, 0f)
        canvas.drawPaint(paint)
    }

    override fun setAlpha(alpha: Int) {}

    override fun setColorFilter(colorFilter: ColorFilter?) {}

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

    override fun start() {
        animator.start()
    }

    override fun stop() {
        animator.cancel()
    }

    override fun isRunning(): Boolean = animator.isRunning

    override fun onTimeUpdate(animation: TimeAnimator, totalTime: Long, deltaTime: Long) {
        x = pixelsPerSecond * totalTime / 1000
        invalidateSelf()
    }
}

class ScrollingProgressBar : ProgressBar {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)

    init {
        this.indeterminateDrawable = ScrollingGradient()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        this.indeterminateDrawable.setBounds(this.left, this.top, this.right, this.bottom)
    }
}

Layout xml (replace com.test.ScrollingProgressBar with the location of code above)

<com.test.ScrollingProgressBar
        android:id="@+id/progressBar1"
        android:background="#464646"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:gravity="center"
        android:indeterminateOnly="true"/>
Twisted89
  • 406
  • 5
  • 14
-1

for performance I would extend the ProgressBar class and override the onDraw method myself. Then draw a Rect with the proper Gradient in the Paint : Canvas's drawRect method where you specify coordinates and the Paint

Here is a good android input to start custom drawing : Custom drawing by Android

And here is a simple start example of a custom drawing view : Simple example using onDraw

So, in code, something like this would do for a static Gradient :

public class MyView extends View {
    private int color1 = 0, color2 = 1;
    private LinearGradient linearGradient = new LinearGradient(0,0,0,0,color1,color2, Shader.TileMode.REPEAT);
    Paint p;
    public MyView(Context context) {
        super(context);
    }

    @Override
    protected synchronized void onDraw(Canvas canvas) {
        p = new Paint();
        p.setDither(true);
        p.setShader(linearGradient);
        canvas.drawRect(0,0,getWidth(),getHeight(),p);
    }

    @Override
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        linearGradient = new LinearGradient(0,heightMeasureSpec/2, widthMeasureSpec,heightMeasureSpec/2,color1,color2, Shader.TileMode.REPEAT);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

You can play with LinearGradient other constructor to get the desired effect (accepts a list of points, you would probably need 3 of them, the one in the middle giving the progress). You can implement the progress with a variable in your view. The onMeasure method allows me to adapt to the view changing it's size. You can create a setProgress(float progress) method that sets a variable progress and invalidates the View :

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.view.View;

public class MyProgressBar extends View {

private int myWidth = 0, myHeight = 0;
private int[] myColors = new int[]{0,1};
private float[] myPositions = new float[]{0.0f,0.0f,1.0f};

private LinearGradient myLinearGradient = new LinearGradient(0,0,myWidth,myHeight/2,myColors,myPositions, Shader.TileMode.REPEAT);
private Paint myPaint = new Paint();

public MyProgressBar(Context context) {
    super(context);
    myPaint.setDither(true);
}

@Override
protected synchronized void onDraw(Canvas canvas) {
    myPaint.setShader(myLinearGradient);
    canvas.drawRect(0,0,getWidth(),getHeight(),p);
}

@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    myWidth = widthMeasureSpec;
    myHeight = heightMeasureSpec;
    myLinearGradient = new LinearGradient(0,0,myWidth,myHeight/2,myColors,myPositions, Shader.TileMode.REPEAT);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// progress must be a percentage, a float between 0.0f and 1.0f
public void setProgress(float progress) {
    myPositions[1] = progress;
    myLinearGradient = new LinearGradient(0,0,myWidth,myHeight/2,myColors,myPositions, Shader.TileMode.REPEAT);
    this.invalidate();
}
}

Of course, you have to use the setProgress(progress) method for it to be dynamic.

c-val
  • 181
  • 1
  • 2
  • 13
  • I tried to use this code, but the animation doesn't work. The color is static. Please show full code – android developer Feb 08 '18 at 21:47
  • 1
    you should never instantiate any variable inside `onDraw` function as it gets called around 60 times per second which means you are creating and allocating new memory each time! – AouledIssa Sep 23 '18 at 21:42
  • Thank you @mohamedaouledissa I removed this ugly thing ;) – c-val Nov 26 '18 at 19:22