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!
To use a custom object animator property you need to create the getter and setter methods as stated in this Stackoverflow answer:
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();
}
}
The above concrete example is a demonstration of Hithredin suggestion to use ObjectAnimator.
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);
}