4

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!

Community
  • 1
  • 1
  • Check this http://stackoverflow.com/questions/4566118/android-custom-animation-for-an-arcshape – Arun Nov 03 '14 at 13:02
  • @RiThBo, you need to create the getter and setter method for your Arc class. I had a variable called "sweepAngle" and I created getSweepAngle() and setSweepAngle(). Your code is very similar to mine, so I believe you need to create getMSweepAngle() and setMSweepAngle(). I've added an answer below to clarify. – Zhang Jun 08 '15 at 15:09
  • See https://medium.com/@dbottillo/animate-android-custom-view-a94473412054. – CoolMind May 14 '20 at 09:29

2 Answers2

2

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);
}
Community
  • 1
  • 1
Zhang
  • 11,549
  • 7
  • 57
  • 87
1

With Handler your message is not garanteed to arrive on time, so the animation may look strange.

  • If you want to keeo your drawArc code, you could use the PropertyAnimation. On the onAnimationUpdate() method, you'll invalidate the view. Take care that the invalidate is neither synchronous guaranteed. If the animation is very fast, it may skip some frames.

  • You have another solution, the Drawable Animation . You'll have the boring task to create as many image as the animation requires, but it'll be easy to implement.

Benoit
  • 1,922
  • 16
  • 25
  • 1
    Wow, I was having the same problem as RiThBo and implemented my own animation method which was off about 8 milliseconds but yeah, following this answer, I was able to use ObjectAnimator to do the animation. It might be inaccurate just like my own custom implementation but I get the benefit of having easing or no easing using Object Animator vs just linear animation with my own custom implementation. It's also shorter to write :P – Zhang Jun 08 '15 at 15:06