在安卓中实现进度条动画更新

80

我在我的应用程序中使用ProgressBar,并在AsyncTask的onProgressUpdate中更新它。到目前为止都还好。

我想要做的是动画地更新进度,使其不仅仅“跳跃”到值,而是平滑地移动到它。

我尝试运行以下代码来实现:

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();
                }
            }
        }

    });
问题在于进度条直到完成其最终值(进度变量)之前不会更新其状态。屏幕上也不显示所有中间状态。调用progressBar.invalidate()也没有帮助。
有什么想法吗?谢谢!
14个回答

198
我使用了Android动画来实现这个功能:
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);
    }

}

并这样调用:

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

注意:如果起始值和结束值太低,无法产生平滑的动画效果,请将它们乘以100或更多。如果这样做,请不要忘记同时将setMax(..)乘以相同的倍数。

1
@EliKonky。干得好。很有效。我有一个问题。我想在进度条达到100(最大值)后隐藏它。如何设置其可见性在达到最大值后消失?请帮我解决。 - Nitesh Kumar
插值时间(interpolatedTime)会获得什么值?它是否取决于您为setDuration()设置的值? - hamid_c
@hamid_c 是的,interpolatedTime 是传递给 setDuration() 的值。 - riteshtch
@EliKonky 正在寻找一种访问动画回调方法以更新另一个视图的方法,applyTransformation() 拯救了我的一天!非常感谢。 - riteshtch
出色的工作完美无缺。 - Asad Mehmood
显示剩余2条评论

60

最简单的方法,使用ObjectAnimator(Java和Kotlin都可以)

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

progressValue 是一个介于0-100之间的整数(默认情况下,上限是100,但您可以使用 Progressbar#setMax() 方法进行更改)。

您还可以通过使用 setInterpolator() 方法设置不同的插值器来改变值改变的方式。以下是不同插值器的可视化效果:https://www.youtube.com/watch?v=6UL7PXdJ6-E


2
在进度条上设置动画的非常简单和简洁的答案。 - scienticious
4
将“maxProgress”设为10000左右会更好,这样50%就是5000。这将使动画更加流畅。 - Arturs Vancans

53

我使用了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();

10
应该是“ofInt”类型,可能是0到100(但你应该在“progreso”上使用“setMax(100)”来确保)。其他方面都很好。比其他答案更好。 - Patrick Boos
1
比AsyncTask好很多 - dasar
1
请查看我的答案以获取正确的实现。 - michal3377
由于某些原因,动画看起来不够流畅。 - Bitcoin Cash - ADA enthusiast

12

这是@Eli Konky的解决方案的改进版本:

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);
    }
}

使用方法:

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

/* Update progress later anywhere in code: */
mProgressAnimation.setProgress(progress);

12
ProgressBar().setProgress(int progress, boolean animate)

安卓已经为你处理好了


你能用这个改变动画的持续时间吗? - Myk
尝试在您的 .xml 文件中添加 android:animationResolution = "timeInMilliseconds",Myk。 - EAM
18
这是针对API 24及以上版本的。 - mamzi

7

用 Kotlin 实现这个的方法

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()

6

我想为那些想要使用数据绑定和进度条动画的人增加额外的价值。

首先创建以下绑定适配器:

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

然后在包含报告状态更新的ViewModel的布局中使用它:

<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" />

ViewModel 本身将使用以下 LiveData 报告状态:

private val _progress = MutableLiveData<Int?>(null)
val progress: LiveData<Int?>
    get() = _

3

编辑:虽然我的答案可行,但Eli Konkys的答案更好。请使用。

如果您的线程在UI线程上运行,则必须放弃UI线程以使视图有机会更新。目前,您告诉进度条"更新为1、更新为2、更新为3",而从未释放UI线程,因此它实际上无法更新。

解决此问题的最佳方法是使用Asynctask,它具有在UI线程和非UI线程上运行的本地方法:

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可能看起来很复杂,但它对于许多不同的任务非常高效,正如在Android API中指定的那样:

"AsyncTask可以正确且轻松地使用UI线程。这个类允许在后台执行操作,并在UI线程上发布结果,而无需操纵线程和/或处理程序。"


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()

1
请阅读如何撰写好的答案?。虽然这个代码块可能回答了OP的问题,但如果您解释一下这段代码与问题中的代码有何不同,您做出了哪些更改,为什么要进行更改以及为什么这样解决问题而不会引入其他问题,那么这个答案将会更加有用。 - Saeed Zhiany

2
你可以尝试使用处理程序/可运行程序代替...
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接