2016-04-23 4 views
0

Я создал репо для этого, чтобы каждый мог проверить это самостоятельно. РЕПО предполагает, что он занимает 1 секунду, чтобы загрузить 20%, так что загрузка будет завершена через 5 секунд:Как плавно переходить к текущему прогрессу для SendingProgressView?

https://github.com/Winghin2517/SendingProgressViewTest.git

Это код SendingProgressView- instamaterial в вы можете найти код на 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, так что, когда картина загрузка, круг прогресса будет нарисован см ниже анимация:

enter image description here

Вы можете видеть, что анимация кажется разрозненной - например, когда прогресс идет от 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.StackOverflowError: stack size 8MB 
                   at android.animation.PropertyValuesHolder.nCallFloatMethod(Native Method) 
                   at android.animation.PropertyValuesHolder.access$400(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.access$400(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.access$400(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.access$400(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.access$400(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$1.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) 

ЦЕЛЬ:

Цель состоит в том, чтобы сделать колесо прогресса плавным, когда оно идет от 20% - 40% - 6 0% - 80% - 100%. Также будет здорово, если он включит интерполятор перерегулирования, так что круг будет рисовать с небольшим перерегулированием каждый раз, чтобы показать движение.

+0

Я только что проверил код ** сглаженная анимация ** выполняется с помощью 'setupSimulateProgressAnimator', что делает анимацию в 2-х, так что если погресс слишком много, анимация не будет гладкой – Abdellah

+0

Я знаю, что setupSimulateProgressAnimator сглаживает анимацию, но это только для целей моделирования, а не для реальных сетевых API вызовов сети, когда прогресс не занимает 2 секунды. Иногда для загрузки может потребоваться 10 секунд. Я пытаюсь имитировать реальные сетевые вызовы здесь, поэтому я извлек код из метода и повторно использовал его в своем настраиваемом методе setCurrentProgress(), код которого был скопирован и вставлен в этот поток. – Simon

ответ

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

enter image description here

Full git patch.

+0

Спасибо.Это выглядит неплохо, проверит его дома позже. Вопрос только в том случае, если Drawprogressanimator был отменен, потому что загрузка происходит быстрее, чем может нарисовать, приведет ли она к неполной дуге? – Simon

+0

Я так не думаю. Сначала значение 'currentProgress', переданное в метод' setCurrentProgress', будет все равно вычерчено; Кроме того, как 'drawFrameForDoneAnimation', так и' drawFinishedState' нарисовать весь цикл выполнения. –

Смежные вопросы