我为此创建了一个回购,所以任何人都可以自己测试一下。回购假设上传20%需要1秒钟,因此上传将在5秒后完成:
https://github.com/Winghin2517/SendingProgressViewTest.git
这是instamaterial的SendingProgressView的代码 - 你可以在github上找到代码here
package io.github.froger.instamaterial.ui.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import io.github.froger.instamaterial.R;
/**
* Created by Miroslaw Stanek on 28.02.15.
*/
public class SendingProgressView extends View {
public static final int STATE_NOT_STARTED = 0;
public static final int STATE_PROGRESS_STARTED = 1;
public static final int STATE_DONE_STARTED = 2;
public static final int STATE_FINISHED = 3;
private static final int PROGRESS_STROKE_SIZE = 10;
private static final int INNER_CIRCLE_PADDING = 30;
private static final int MAX_DONE_BG_OFFSET = 800;
private static final int MAX_DONE_IMG_OFFSET = 400;
private int state = STATE_NOT_STARTED;
private float currentProgress = 0;
private float currentDoneBgOffset = MAX_DONE_BG_OFFSET;
private float currentCheckmarkOffset = MAX_DONE_IMG_OFFSET;
private Paint progressPaint;
private Paint doneBgPaint;
private Paint maskPaint;
private RectF progressBounds;
private Bitmap checkmarkBitmap;
private Bitmap innerCircleMaskBitmap;
private int checkmarkXPosition = 0;
private int checkmarkYPosition = 0;
private Paint checkmarkPaint;
private Bitmap tempBitmap;
private Canvas tempCanvas;
private ObjectAnimator simulateProgressAnimator;
private ObjectAnimator doneBgAnimator;
private ObjectAnimator checkmarkAnimator;
private OnLoadingFinishedListener onLoadingFinishedListener;
public SendingProgressView(Context context) {
super(context);
init();
}
public SendingProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SendingProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SendingProgressView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setupProgressPaint();
setupDonePaints();
setupSimulateProgressAnimator();
setupDoneAnimators();
}
private void setupProgressPaint() {
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setColor(0xffffffff);
progressPaint.setStrokeWidth(PROGRESS_STROKE_SIZE);
}
private void setupSimulateProgressAnimator() {
simulateProgressAnimator = ObjectAnimator.ofFloat(this, "currentProgress", 0, 100).setDuration(2000);
simulateProgressAnimator.setInterpolator(new AccelerateInterpolator());
simulateProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
changeState(STATE_DONE_STARTED);
}
});
}
private void setupDonePaints() {
doneBgPaint = new Paint();
doneBgPaint.setAntiAlias(true);
doneBgPaint.setStyle(Paint.Style.FILL);
doneBgPaint.setColor(0xff39cb72);
checkmarkPaint = new Paint();
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}
private void setupDoneAnimators() {
doneBgAnimator = ObjectAnimator.ofFloat(this, "currentDoneBgOffset", MAX_DONE_BG_OFFSET, 0).setDuration(300);
doneBgAnimator.setInterpolator(new DecelerateInterpolator());
checkmarkAnimator = ObjectAnimator.ofFloat(this, "currentCheckmarkOffset", MAX_DONE_IMG_OFFSET, 0).setDuration(300);
checkmarkAnimator.setInterpolator(new OvershootInterpolator());
checkmarkAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
changeState(STATE_FINISHED);
}
});
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateProgressBounds();
setupCheckmarkBitmap();
setupDoneMaskBitmap();
resetTempCanvas();
}
private void updateProgressBounds() {
progressBounds = new RectF(
PROGRESS_STROKE_SIZE, PROGRESS_STROKE_SIZE,
getWidth() - PROGRESS_STROKE_SIZE, getWidth() - PROGRESS_STROKE_SIZE
);
}
private void setupCheckmarkBitmap() {
checkmarkBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_done_white_48dp);
checkmarkXPosition = getWidth() / 2 - checkmarkBitmap.getWidth() / 2;
checkmarkYPosition = getWidth() / 2 - checkmarkBitmap.getHeight() / 2;
}
private void setupDoneMaskBitmap() {
innerCircleMaskBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
Canvas srcCanvas = new Canvas(innerCircleMaskBitmap);
srcCanvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - INNER_CIRCLE_PADDING, new Paint());
}
private void resetTempCanvas() {
tempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
tempCanvas = new Canvas(tempBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
if (state == STATE_PROGRESS_STARTED) {
drawArcForCurrentProgress();
} else if (state == STATE_DONE_STARTED) {
drawFrameForDoneAnimation();
postInvalidate();
} else if (state == STATE_FINISHED) {
drawFinishedState();
}
canvas.drawBitmap(tempBitmap, 0, 0, null);
}
private void drawArcForCurrentProgress() {
tempCanvas.drawArc(progressBounds, -90f, 360 * currentProgress / 100, false, progressPaint);
}
private void drawFrameForDoneAnimation() {
tempCanvas.drawCircle(getWidth() / 2, getWidth() / 2 + currentDoneBgOffset, getWidth() / 2 - INNER_CIRCLE_PADDING, doneBgPaint);
tempCanvas.drawBitmap(checkmarkBitmap, checkmarkXPosition, checkmarkYPosition + currentCheckmarkOffset, checkmarkPaint);
tempCanvas.drawBitmap(innerCircleMaskBitmap, 0, 0, maskPaint);
tempCanvas.drawArc(progressBounds, 0, 360f, false, progressPaint);
}
private void drawFinishedState() {
tempCanvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - INNER_CIRCLE_PADDING, doneBgPaint);
tempCanvas.drawBitmap(checkmarkBitmap, checkmarkXPosition, checkmarkYPosition, checkmarkPaint);
tempCanvas.drawArc(progressBounds, 0, 360f, false, progressPaint);
}
private void changeState(int state) {
if (this.state == state) {
return;
}
tempBitmap.recycle();
resetTempCanvas();
this.state = state;
if (state == STATE_PROGRESS_STARTED) {
setCurrentProgress(0);
simulateProgressAnimator.start();
} else if (state == STATE_DONE_STARTED) {
setCurrentDoneBgOffset(MAX_DONE_BG_OFFSET);
setCurrentCheckmarkOffset(MAX_DONE_IMG_OFFSET);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(doneBgAnimator, checkmarkAnimator);
animatorSet.start();
} else if (state == STATE_FINISHED) {
if (onLoadingFinishedListener != null) {
onLoadingFinishedListener.onLoadingFinished();
}
}
}
public void simulateProgress() {
changeState(STATE_PROGRESS_STARTED);
}
public void setCurrentProgress(float currentProgress) {
this.currentProgress = currentProgress;
postInvalidate();
}
public void setCurrentDoneBgOffset(float currentDoneBgOffset) {
this.currentDoneBgOffset = currentDoneBgOffset;
postInvalidate();
}
public void setCurrentCheckmarkOffset(float currentCheckmarkOffset) {
this.currentCheckmarkOffset = currentCheckmarkOffset;
postInvalidate();
}
public void setOnLoadingFinishedListener(OnLoadingFinishedListener onLoadingFinishedListener) {
this.onLoadingFinishedListener = onLoadingFinishedListener;
}
public interface OnLoadingFinishedListener {
public void onLoadingFinished();
}
}
我设法在我的应用程序中实现它并将其绑定到我的上传API,以便在上传图片时,将绘制进度圈,请参阅下面的动画:
你可以看到动画似乎脱节 - 例如,当进度从35%变为50%时,你可以看到它没有平滑的动画,它只是绘制更多的弧来表明它现在是50%。
在我的应用中,我使用名为SendingProgressView
的{{1}}内的方法设置视图的setCurrentProgress
,具体取决于从网络返回的值,以便我的图像进展上传。方法如下所示:
currentProgress
每次查看public void setCurrentProgress(float currentProgress) {
this.currentProgress = currentProgress;
postInvalidate();
}
本身时,它会绘制更多的弧,但它不会为弧本身的绘制设置动画。我希望它能更顺利地为进度制作动画。
我尝试通过将postInvalidates
的代码更改为使用setCurrentProgress
来设置弧的绘制动画:
ObjectAnimator
但应用程序最终崩溃了:
public void setCurrentProgress(float currentProgress) {
ObjectAnimator simulateProgressAnimator =
ObjectAnimator.ofFloat(this, "currentProgress", this.currentProgress, currentProgress).setDuration(200);
simulateProgressAnimator.setInterpolator(new AccelerateInterpolator());
this.currentProgress = currentProgress;
if (!simulateProgressAnimator.isStarted()) {
simulateProgressAnimator.start();
}
}
目的:
这里的目标是让进度轮在20% - 40% - 60% - 80% - 100%之间平滑动画。如果它包含一个过冲插补器,那么每次显示运动时圆圈都会画出一点过冲,这也会很棒。
答案 0 :(得分:4)
在这种情况下,使用ValueAnimator
应该是一个非常好的解决方案。
private ValueAnimator drawProgressAnimator;
public void setCurrentProgress(float currentProgress, boolean smoothProgress) {
//Log.d("setCurrentProgress", "Current value = " + this.currentProgress + "; new target value = " + currentProgress);
if (drawProgressAnimator != null) {
drawProgressAnimator.cancel();
drawProgressAnimator = null;
}
if (smoothProgress) {
drawProgressAnimator = ValueAnimator.ofFloat(this.currentProgress, currentProgress);
drawProgressAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
long duration = (long) Math.abs(1500 * ((currentProgress - this.currentProgress) / 100)); // 1.5 second for 100% progress, 750ms for 50% progress and so on
drawProgressAnimator.setDuration(duration);
drawProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//Log.i("onAnimationUpdate", "getAnimatedValue() = " + ((float) animation.getAnimatedValue()));
SendingProgressView.this.currentProgress = (float) animation.getAnimatedValue();
postInvalidate();
}
});
drawProgressAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
drawProgressAnimator = null;
}
});
drawProgressAnimator.start();
} else {
this.currentProgress = currentProgress;
postInvalidate();
}
}