Search code examples
androidandroid-animationandroid-custom-viewobjectanimator

Android Progressbar (custom view) value animation not working - Object Animator in View after invalidate


I need to animate progress bar value changed while valueproperty gets changed, my custom view is given below,

public class ProgressBar extends View {

    public ProgressBar(Context context) {
        this(context, null);
    }

    public ProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    ObjectAnimator animator;
    double value = 50;

    public double getValue() {
        return value;
    }

    public void setTargetValue(double targetValue) {
        animator = ObjectAnimator.ofFloat(this, "value", (float) this.value,(float) targetValue);
        animator.setDuration(1500);
        animator.start();
        this.value = targetValue;
    }
    public void setValue(double tempValue) {
        setTargetValue(tempValue);
        this.invalidate();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint =  new Paint();
        paint.setStrokeWidth(3);
        RectF borderRectF = new RectF(50,100,400,200);
        RectF backgroundRectF = new RectF(53,103,397,197);
        RectF filledRectF = new RectF(53,103,53,197);
        paint.setColor(Color.LTGRAY);
        canvas.drawRect(borderRectF, paint);
        paint.setStrokeWidth(0);
        paint.setColor(Color.WHITE);
        canvas.drawRect(backgroundRectF, paint);
        paint.setColor(Color.BLUE);
        filledRectF = getfilledRect();
        canvas.drawRect(filledRectF, paint);
    }

   private RectF getfilledRect(){
       float filledValue = (float)(53+(3.44 * this.value));
       filledValue = Math.min(filledValue,397);
        return new RectF(53,103,filledValue,197);
    }
}

but animation is not working, am i miss something, or can i do it differently?


Solution

  • You need two functions instead of this one. One function should be called with the new target for your value, and the other function should be used to implement each step along the way. Use ObjectAnimator in the first one, and it will call your second function many times for each incremental step. Like this:

    public void setProgress(float progress) {
        animator = ObjectAnimator.ofFloat(this, "value", this.value, progress);
        animator.setDuration(1500);
        animator.start();
    }
    
    public void setValue(float value) {
         this.value = value;
         invalidate();
    }
    
    private RectF getfilledRect() {
       float filledValue = 53f + (3.44f * this.value);
       filledValue = Math.min(filledValue, 397f);
       return new RectF(53f, 103f, filledValue, 197f);
    }
    

    A few notes:

    • You don't need to call "setTarget(this)" because you already set this as the target, in the first parameter to ofFloat.
    • You might want to delay setting your field "value" until the animation is complete. You can use an AnimationListener to do this, overriding onAnimationEnd. As it is, the field will be set to the new target value before the UI represents this.
    • The signature of "setValue" must be exactly as shown, because that is how ObjectAnimator.ofFloat works: it looks for a setter of the named property that takes a float and returns void.

    EDIT

    Ah, I see, you're making your own progress bar. In that case, you were right to call invalidate, since you are overriding onDraw. I've modified my answer above, changing "setTargetValue" to "setProgress". This is a function that should only be called from OUTSIDE this class -- whoever knows what the progress is. You do NOT want setProgress to call setValue, or vice versa.

    New notes:

    • You should just use float values everywhere and not double, since they eventually get used in a RectF anyway. If you add "f" after a number, then Java interprets it as a float and not a double, and you don't have to do casting in your equations.
    • Since you are overriding onDraw, your intermediate setValue function only needs to set your field "value", and invalidate the progress bar.