Animate drawing of circle/ arc on canvas

user2442638 picture user2442638 · Nov 17, 2013 · Viewed 9.1k times · Source

Update: 20/11/13: This is still unresolved.

I am trying to animate the creation of a circle in a custom view. I would like to animate the creation of the circumference - at the beginning of the animation there is an arc, and by the end of the animation, the circle is complete.

I did this sucessfully by following this answer - https://stackoverflow.com/a/11168363/2442638 - and just adding a repeating Handler to increase the sweepAngle and call invalidate();

However, this doesn't work the way I would like it to as I cannot set the duration to complete the circle.

This is my current code:

  Path mOuterCirclePath = new Path();
        final RectF mOval = new RectF();
        int mSweepAngle = 0;
        int mStartAngle = -90;

    @Override
        protected void onDraw(Canvas canvas) {

                mOval.set(0, 0, mBorderRect.right, mBorderRect.bottom); //mBorderRect is the outside border of my view
                mOuterCirclePath.arcTo(mOval, 0, 0, true);
                canvas.drawArc(mOval, -mStartAngle, mSweepAngle, false,
                        mOuterCirclePaint);
    }

        public void drawOuterCircle() {

                startUpdateOuterCircle();

        }

        Runnable mCircleInvalidator = new Runnable() {
            @Override
            public void run() {
                              if (mSweepAngle <= 360) {
                                mSweepAngle++
    }
                invalidate();
                mHandler.postDelayed(mCircleInvalidator, 20); 
            }
        };

        void startUpdateOuterCircle() {
            mCircleInvalidator.run();
        }

        void stopUpdateOuterCircle() {
            mHandler.removeCallbacks(mCircleInvalidator);
        }

The main question is: How do I set the duration for the animation? I would like this to be easily changeable, like it is in the animator classes.

P.S. As far as I'm aware I can't use any of the animators such as ViewPropertyAnimator of 'ObjectAnimator' for this. Please correct me if I'm wrong!

Answer

Zhang picture Zhang · Jun 8, 2015

To use a custom object animator property you need to create the getter and setter methods as stated in this Stackoverflow answer:

Android Property Animation

In my case, I had a CircleView class and sweepAngle as a variable, something like this:

public class CircleView extends View
{
    public float sweepAngle = 0.0f; // start at 0 degrees

    ...

    // ---------------------------------------------------
    // getter and setter method to turn "sweepAngle"
    // into a property for ObjectAnimator to use
    // ---------------------------------------------------
    public float getSweepAngle()
    {
        return sweepAngle;
    }

    public void setSweepAngle(float angle)
    {
        sweepAngle = angle;
    }

    // ---------------------------------------------------
    // animation method to be called outside this class
    // ---------------------------------------------------
    public void animateArc(float fromAngle, float toAngle, long duration)
    {
        sweepAngle = fromAngle;

        invalidate();

        ObjectAnimator anim = ObjectAnimator.ofFloat(this, "sweepAngle", fromAngle, toAngle);
        anim.setDuration(duration);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                // calling invalidate(); will trigger onDraw() to execute
                invalidate();
            }
        });
        anim.start();
    }
}

Note:

The above concrete example is a demonstration of Hithredin suggestion to use ObjectAnimator.

Custom Poor-Man's Implementation

I suggest you don't use this block of code below but I'm including it in case you were wondering how I did my own custom implementation which I found was off by 8 milliseconds and only allows linear interpolation (no ease in/ease out), it was like the above code but slightly different:

public void animateArc(float fromAngle, float toAngle, long duration)
{
    sweepAngle = fromAngle;

    invalidate();

    // ---------------------------------------------------------------
    // Note: might want to change use absolute value for totalAngle
    // in case you want the animation to play backwards
    // ---------------------------------------------------------------
    float totalAngle = toAngle - fromAngle;

    updateArc(totalAngle, duration);
}

public void updateArc(final float totalAngle, final long durationInMilliSeconds)
{
    final long stepMilliseconds = 1;

    handler.postDelayed(new Runnable()
    {
        @Override
        public void run()
        {
            // ------------------------------------------------
            // 17790.0 is a number I fine tuned and came out
            // with on my Android phone to get the animation
            // as close as possible to the animation
            // duration specified
            // ------------------------------------------------
            double stepAngle = totalAngle / 1000.0 * (17790.0 / durationInMilliSeconds);

            sweepAngle += stepAngle;
            animationTime += stepMilliseconds;
            invalidate();

            if(animationTime < durationInMilliSeconds && sweepAngle < totalAngle)
            {
                updateArc(totalAngle, durationInMilliSeconds);
            }
            else
            {
                // --------------------------------------------------------
                // time duration reached, stop incrementing/decrementing
                // angle and reset animation time back to 0
                // --------------------------------------------------------
                animationTime = 0;
            }
        }
    }, 0);
}