80

I am using a ProgressBar in my application which I update in onProgressUpdate of an AsyncTask. So far so good.

What I want to do is to animate the progress update, so that it does not just "jump" to the value but smoothly moves to it.

I tried doing so running the following code:

this.runOnUiThread(new Runnable() {

        @Override
        public void run() {
            while (progressBar.getProgress() < progress) {
                progressBar.incrementProgressBy(1);
                progressBar.invalidate();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

    });

The problem is that the progress bar does not update its state until it finished its final value (progress variable). All states in between are not displayed on the screen. Calling progressBar.invalidate() didn't help either.

Any ideas? Thanks!

André Sousa
  • 1,692
  • 1
  • 12
  • 23
user1033552
  • 1,499
  • 1
  • 14
  • 23

14 Answers14

197

I used android Animation for this:

public class ProgressBarAnimation extends Animation{
    private ProgressBar progressBar;
    private float from;
    private float  to;

    public ProgressBarAnimation(ProgressBar progressBar, float from, float to) {
        super();
        this.progressBar = progressBar;
        this.from = from;
        this.to = to;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        float value = from + (to - from) * interpolatedTime;
        progressBar.setProgress((int) value);
    }

}

and call it like so:

ProgressBarAnimation anim = new ProgressBarAnimation(progress, from, to);
anim.setDuration(1000);
progress.startAnimation(anim);

Note: if from and to value are too low to produce smooth animation just multiply them by a 100 or so. If you do so, don't forget to multiply setMax(..) as well.

Eli Konky
  • 2,697
  • 1
  • 20
  • 17
  • 1
    @EliKonky. Nice work. Works fine. I have a question. I want to hide the progress bar after it reaches 100 (max value). How to set its visibility gone after it reaches max? Please help me out. – Nitesh Kumar Aug 01 '15 at 18:52
  • what values interpolatedTime will get???is it depend to the value that u set for setDuration()??? – hamid_c Jan 18 '16 at 15:42
  • @hamid_c yes `interpolatedTime` is the value passed to `setDuration()` – riteshtch Nov 23 '16 at 05:50
  • @EliKonky Was finding a way to access the animation callback method to update another view, `applyTransformation()` saved my day! Thanks a ton. – riteshtch Nov 23 '16 at 05:52
  • if anybody want to animate secondary progress then change in ProgressBarAnimation class `progressBar.setSecondaryProgress((int)value); ` instead of `progressBar.setProgress((int) value);` – Zahidul May 08 '18 at 06:57
  • Keep in mind that #getProgress() returns a wrong (possibly undesired) value when the animation is still running. – Tobias Baumeister Oct 19 '18 at 16:39
  • outstanding worked perfectly – Asad Mehmood Jun 03 '22 at 08:02
59

The simplest way, using ObjectAnimator (both Java and Kotlin):

ObjectAnimator.ofInt(progressBar, "progress", progressValue)
    .setDuration(300)
    .start();

where progressValue is integer within range 0-100 (the upper limit is set to 100 by default but you can change it with Progressbar#setMax() method)

You can also change the way how values are changing by setting different interpolator with setInterpolator() method. Here is visualization of different interpolators: https://www.youtube.com/watch?v=6UL7PXdJ6-E

michal3377
  • 1,394
  • 12
  • 19
53

I use an ObjectAnimator

private ProgressBar progreso;
private ObjectAnimator progressAnimator;
progreso = (ProgressBar)findViewById(R.id.progressbar1);
progressAnimator = ObjectAnimator.ofInt(progreso, "progress", 0,1);
progressAnimator.setDuration(7000);
progressAnimator.start();
Nil
  • 312
  • 4
  • 19
kenshinsoto
  • 547
  • 4
  • 4
12

Here is an improved version of @Eli Konky solution:

public class ProgressBarAnimation extends Animation {
    private ProgressBar mProgressBar;
    private int mTo;
    private int mFrom;
    private long mStepDuration;

    /**
     * @param fullDuration - time required to fill progress from 0% to 100%
     */
    public ProgressBarAnimation(ProgressBar progressBar, long fullDuration) {
        super();
        mProgressBar = progressBar;
        mStepDuration = fullDuration / progressBar.getMax();
    }


    public void setProgress(int progress) {
        if (progress < 0) {
            progress = 0;
        }

        if (progress > mProgressBar.getMax()) {
            progress = mProgressBar.getMax();
        }

        mTo = progress;

        mFrom = mProgressBar.getProgress();
        setDuration(Math.abs(mTo - mFrom) * mStepDuration);
        mProgressBar.startAnimation(this);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float value = mFrom + (mTo - mFrom) * interpolatedTime;
        mProgressBar.setProgress((int) value);
    }
}

And usage:

ProgressBarAnimation mProgressAnimation = new ProgressBarAnimation(mProgressBar, 1000);
...

/* Update progress later anywhere in code: */
mProgressAnimation.setProgress(progress);
Community
  • 1
  • 1
a.ch.
  • 8,285
  • 5
  • 40
  • 53
11
ProgressBar().setProgress(int progress, boolean animate)

Android has taken care of that for you

EAM
  • 361
  • 2
  • 5
7

A Kotlin way of doing this

import kotlinx.android.synthetic.main.activity.*

progressBar.max = value * 100
progressBar.progress = 0

val progressAnimator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, progressBar.progress + 100)
progressAnimator.setDuration(7000)
progressAnimator.start()
Co2p
  • 73
  • 1
  • 5
6

I just wanted to add an extra value for those who want to use Data Binding with a progress bar animation.

First create the following binding adapter:

@BindingAdapter("animatedProgress")
fun setCustomProgressBar(progressBar: ProgressBar, progress: Int) {
    ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, progress).apply {
        duration = 500
        interpolator = DecelerateInterpolator()
    }.start()
}

And then use it in the layout which contains the ViewModel that reports the status updates:

<ProgressBar
    android:id="@+id/progress_bar_horizontal"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="0dp"
    android:layout_height="30dp"
    android:layout_marginStart="32dp"
    android:layout_marginEnd="32dp"
    android:indeterminate="false"
    android:max="100"
    app:animatedProgress="@{viewModel.progress ?? 0}"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

The ViewModel itself will report the status with the following LiveData:

private val _progress = MutableLiveData<Int?>(null)
val progress: LiveData<Int?>
    get() = _
GoRoS
  • 5,183
  • 2
  • 43
  • 66
3

EDIT: While my answer works, Eli Konkys answer is better. Use it.

if your thread runs on the UI thread then it must surrender the UI thread to give the views a chance to update. Currently you tell the progress bar "update to 1, update to 2, update to 3" without ever releasing the UI-thread so it actually can update.

The best way to solve this problem is to use Asynctask, it has native methods that runs both on and off the UI thread:

public class MahClass extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... params) {
        while (progressBar.getProgress() < progress) {
            publishProgress();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        progressBar.incrementProgressBy(1);
    }
}

AsyncTask might seem complicated at first, but it is really efficient for many different tasks, or as specified in the Android API:

"AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers."

pgsandstrom
  • 14,361
  • 13
  • 70
  • 104
2

Simple Kotlin solution

if (newValue != currentValue) {
    ObjectAnimator.ofInt(bar, "progress", currentValue, newValue)
        .setDuration(500L) // ms
        .start()
}

Even simpler:

ObjectAnimator.ofInt(bar, "progress", currentValue, newValue).apply {
    duration = 500L
    start()
}
Gibolt
  • 42,564
  • 15
  • 187
  • 127
2
val animator = ValueAnimator.ofInt(0, 10)
animator.duration = 2000 //set duration in milliseconds
animator.addUpdateListener {
    progressbar.progress = Integer.parseInt(animator.animatedValue.toString())
}
animator.start()
Saeed Zhiany
  • 2,051
  • 9
  • 30
  • 41
  • 1
    Please read [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer). While this code block may answer the OP's question, this answer would be much more useful if you explain how this code is different from the code in the question, what you've changed, why you've changed it and why that solves the problem without introducing others. – Saeed Zhiany Jul 22 '22 at 05:12
2

You could try using a handler / runnable instead...

private Handler h = new Handler();
private Runnable myRunnable = new Runnable() {
        public void run() {
            if (progressBar.getProgress() < progress) {
                        progressBar.incrementProgressBy(1);
                        progressBar.invalidate();
            h.postDelayed(myRunnable, 10); //run again after 10 ms
        }
    };

//trigger runnable in your code
h.postDelayed(myRunnable, 10); 

//don't forget to cancel runnable when you reach 100%
h.removeCallbacks(myRunnable);
Damian
  • 8,062
  • 4
  • 42
  • 43
1

Here is an improved version of a.ch. solution where you can also use rotation for circular ProgressBar. Sometimes it's required to set constant progress and change only rotation or even both progress and rotation. It is also possible to force clockwise or counter clockwise rotation. I hope it will help.

public class ProgressBarAnimation extends Animation {
    private ProgressBar progressBar;
    private int progressTo;
    private int progressFrom;
    private float rotationTo;
    private float rotationFrom;
    private long animationDuration;
    private boolean forceClockwiseRotation;
    private boolean forceCounterClockwiseRotation;

    /**
     * Default constructor
     * @param progressBar ProgressBar object
     * @param fullDuration - time required to change progress/rotation
     */
    public ProgressBarAnimation(ProgressBar progressBar, long fullDuration) {
        super();
        this.progressBar = progressBar;
        animationDuration = fullDuration;
        forceClockwiseRotation = false;
        forceCounterClockwiseRotation = false;
    }

    /**
     * Method for forcing clockwise rotation for progress bar
     * Method also disables forcing counter clockwise rotation
     * @param forceClockwiseRotation true if should force clockwise rotation for progress bar
     */
    public void forceClockwiseRotation(boolean forceClockwiseRotation) {
        this.forceClockwiseRotation = forceClockwiseRotation;

        if (forceClockwiseRotation && forceCounterClockwiseRotation) {
            // Can't force counter clockwise and clockwise rotation in the same time
            forceCounterClockwiseRotation = false;
        }
    }

    /**
     * Method for forcing counter clockwise rotation for progress bar
     * Method also disables forcing clockwise rotation
     * @param forceCounterClockwiseRotation true if should force counter clockwise rotation for progress bar
     */
    public void forceCounterClockwiseRotation(boolean forceCounterClockwiseRotation) {
        this.forceCounterClockwiseRotation = forceCounterClockwiseRotation;

        if (forceCounterClockwiseRotation && forceClockwiseRotation) {
            // Can't force counter clockwise and clockwise rotation in the same time
            forceClockwiseRotation = false;
        }
    }

    /**
     * Method for setting new progress and rotation
     * @param progress new progress
     * @param rotation new rotation
     */
    public void setProgressAndRotation(int progress, float rotation) {

        if (progressBar != null) {
            // New progress must be between 0 and max
            if (progress < 0) {
                progress = 0;
            }
            if (progress > progressBar.getMax()) {
                progress = progressBar.getMax();
            }
            progressTo = progress;

            // Rotation value should be between 0 and 360
            rotationTo = rotation % 360;

            // Current rotation value should be between 0 and 360
            if (progressBar.getRotation() < 0) {
                progressBar.setRotation(progressBar.getRotation() + 360);
            }
            progressBar.setRotation(progressBar.getRotation() % 360);

            progressFrom = progressBar.getProgress();
            rotationFrom = progressBar.getRotation();

            // Check for clockwise rotation
            if (forceClockwiseRotation && rotationTo < rotationFrom) {
                rotationTo += 360;
            }

            // Check for counter clockwise rotation
            if (forceCounterClockwiseRotation && rotationTo > rotationFrom) {
                rotationTo -= 360;
            }

            setDuration(animationDuration);
            progressBar.startAnimation(this);
        }
    }

    /**
     * Method for setting only progress for progress bar
     * @param progress new progress
     */
    public void setProgressOnly(int progress) {
        if (progressBar != null) {
            setProgressAndRotation(progress, progressBar.getRotation());
        }
    }

    /**
     * Method for setting only rotation for progress bar
     * @param rotation new rotation
     */
    public void setRotationOnly(float rotation) {
        if (progressBar != null) {
            setProgressAndRotation(progressBar.getProgress(), rotation);
        }
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float progress = progressFrom + (progressTo - progressFrom) * interpolatedTime;
        float rotation = rotationFrom + (rotationTo - rotationFrom) * interpolatedTime;

        // Set new progress and rotation
        if (progressBar != null) {
            progressBar.setProgress((int) progress);
            progressBar.setRotation(rotation);
        }
    }
}

Usage:

ProgressBarAnimation progressBarAnimation = new ProgressBarAnimation(progressBar, 1000);

// Example 1
progressBarAnimation.setProgressAndRotation(newProgress, newRotation);

// Example 2
progressBarAnimation.setProgressOnly(newProgress);

// Example 3
progressBarAnimation.setRotationOnly(newRotation);
1

Similar with Kotlin on the UI thread

activity?.runOnUiThread {
        ObjectAnimator.ofInt(binding.progressAudio, "progress", currentPosition)
            .setDuration(100)
            .start();
    }
fullmoon
  • 8,030
  • 5
  • 43
  • 58
0

My solution with custom ProgressBar. You can specify animation (animationLength) legth and "smoothness" (animationSmoothness) using attributes (when you use it in XML layout)

AnimatedProgressBar.java

public class AnimatedProgressBar extends ProgressBar {
private static final String TAG = "AnimatedProgressBar";

private static final int BASE_ANIMATION_DURATION = 1000;
private static final int BASE_PROGRESS_SMOOTHNESS = 50;

private int animationDuration = BASE_ANIMATION_DURATION;
private int animationSmoothness = BASE_PROGRESS_SMOOTHNESS;

public AnimatedProgressBar(Context context) {
    super(context);
    init();
}

public AnimatedProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    obtainAnimationAttributes(attrs);
    init();
}

public AnimatedProgressBar(Context context, AttributeSet attrs, int theme) {
    super(context, attrs, theme);
    obtainAnimationAttributes(attrs);
    init();
}

private void obtainAnimationAttributes(AttributeSet attrs) {
    for(int i = 0; i < attrs.getAttributeCount(); i++) {
        String name = attrs.getAttributeName(i);

        if (name.equals("animationDuration")) {
            animationDuration = attrs.getAttributeIntValue(i, BASE_ANIMATION_DURATION);
        } else if (name.equals("animationSmoothness")) {
            animationSmoothness = attrs.getAttributeIntValue(i, BASE_PROGRESS_SMOOTHNESS);
        }
    }
}

private void init() {

}

@Override
public synchronized void setMax(int max) {
    super.setMax(max * animationSmoothness);
}

public void makeProgress(int progress) {
    ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress * animationSmoothness);
    objectAnimator.setDuration(animationDuration);
    objectAnimator.setInterpolator(new DecelerateInterpolator());
    objectAnimator.start();
}}

values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="AnimatedProgressBar">
        <attr name="animationDuration" format="integer" />
        <attr name="animationSmoothness" format="integer" />
    </declare-styleable>
</resources>