如何流畅地动画化发送 ProgressView 的当前进度?
How do I animate smoothly the current progress for SendingProgressView?
我为此创建了一个存储库,这样任何人都可以自己测试。 repo 假定上传 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
中称为 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();
}
}
但应用程序最终崩溃了:
04-23 17:40:35.938 14196-14196/com.myapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myapp, PID: 14196
java.lang.WhosebugError: stack size 8MB
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com
04-23 17:40:36.068 14196-14196/com.myapp E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
04-23 17:40:36.068 14196-14196/com.myapp E/AndroidRuntime: Error reporting crash
android.os.TransactionTooLargeException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:496)
at android.app.ActivityManagerProxy.handleApplicationCrash(ActivityManagerNative.java:4164)
at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:89)
at com.crashlytics.android.core.CrashlyticsUncaughtExceptionHandler.uncaughtException(CrashlyticsUncaughtExceptionHandler.java:249)
at com.myapp.activities.Application.uncaughtException(Application.java:56)
at com.flurry.sdk.mc.b(SourceFile:96)
at com.flurry.sdk.mc.b(SourceFile:19)
at com.flurry.sdk.mc$a.uncaughtException(SourceFile:107)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
OBJECTIVE:
这里的目标是让进度轮在 20% - 40% - 60% - 80% - 100% 时平滑地动画。如果它包含一个过冲插值器,那么每次绘制圆时都会有一点过冲以显示运动,这也会很棒。
在这种情况下,使用 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();
}
}
我为此创建了一个存储库,这样任何人都可以自己测试。 repo 假定上传 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
中称为 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();
}
}
但应用程序最终崩溃了:
04-23 17:40:35.938 14196-14196/com.myapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myapp, PID: 14196
java.lang.WhosebugError: stack size 8MB
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com.myapp.customshapes.SendingProgressView.setCurrentProgress(SendingProgressView.java:222)
at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method)
at android.animation.PropertyValuesHolder.access0(PropertyValuesHolder.java:39)
at android.animation.PropertyValuesHolder$FloatPropertyValuesHolder.setAnimatedValue(PropertyValuesHolder.java:1298)
at android.animation.ObjectAnimator.animateValue(ObjectAnimator.java:956)
at android.animation.ValueAnimator.setCurrentFraction(ValueAnimator.java:602)
at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:550)
at android.animation.ValueAnimator.start(ValueAnimator.java:1039)
at android.animation.ValueAnimator.start(ValueAnimator.java:1050)
at android.animation.ObjectAnimator.start(ObjectAnimator.java:829)
at com
04-23 17:40:36.068 14196-14196/com.myapp E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
04-23 17:40:36.068 14196-14196/com.myapp E/AndroidRuntime: Error reporting crash
android.os.TransactionTooLargeException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:496)
at android.app.ActivityManagerProxy.handleApplicationCrash(ActivityManagerNative.java:4164)
at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:89)
at com.crashlytics.android.core.CrashlyticsUncaughtExceptionHandler.uncaughtException(CrashlyticsUncaughtExceptionHandler.java:249)
at com.myapp.activities.Application.uncaughtException(Application.java:56)
at com.flurry.sdk.mc.b(SourceFile:96)
at com.flurry.sdk.mc.b(SourceFile:19)
at com.flurry.sdk.mc$a.uncaughtException(SourceFile:107)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
OBJECTIVE:
这里的目标是让进度轮在 20% - 40% - 60% - 80% - 100% 时平滑地动画。如果它包含一个过冲插值器,那么每次绘制圆时都会有一点过冲以显示运动,这也会很棒。
在这种情况下,使用 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();
}
}