0

I am trying to make a metronome with a needle moving like an upside down pendulum. I have tried RotationAnimation of android to do this but it seems to slow down after few runs. I have tried both LinearInterpolator and a custom interpolator(MetronomeInterpolator).

The following code is taken from Android How to rotate needle when speed changes?, thanks IHaN JiTHiN.

RotateAnimation needleDeflection = new RotateAnimation(
            this.previousAngle, this.currentAngle, this.pivotX, this.pivotY) {
        protected void applyTransformation(float interpolatedTime,
                Transformation t) {
            currentDegrees = previousAngle + (currentAngle - previousAngle)
                    * interpolatedTime;
            currentValue = (((currentDegrees - minAngle) * (maxValue - minValue)) / (maxAngle - minAngle))
                    + minValue;
            if (NDL != null)
                NDL.onDeflect(currentDegrees, currentValue);
            super.applyTransformation(interpolatedTime, t);
        }

    };

    needleDeflection.setAnimationListener(new AnimationListener() {
        @Override
        public void onAnimationStart(Animation arg0) {
        }

        @Override
        public void onAnimationRepeat(Animation arg0) {
        }

        @Override
        public void onAnimationEnd(Animation arg0) {
            previousAngle = currentAngle;
        }
    });
    needleDeflection.setDuration(this.deflectTime);
    needleDeflection.setInterpolator(new MetronomeInterpolator());
    needleDeflection.setFillBefore(true);
    needleDeflection.setFillAfter(false);
    needleDeflection.setStartOffset(0);
    needleDeflection.setRepeatMode(Animation.RESTART);
    needleDeflection.setRepeatCount(Animation.INFINITE);
    this.gaugeNeedle.startAnimation(needleDeflection);
    this.gaugeNeedle.refreshDrawableState();

The animation will restart infinite times.

public class MetronomeInterpolator implements Interpolator {
private double p=0.5;

@Override
public float getInterpolation(float t) {
    // a: amplitude, p: period/2
    //https://stackoverflow.com/questions/1073606/is-there-a-one-line-function-that-generates-a-triangle-wave
    double a=1;
    return (float)((a/p) * (p - Math.abs(t % (2*p) - p)));
}
}

The MetronomeInterpolator is forming a triangular wave. The formula is taken from Is there a one-line function that generates a triangle wave?, thanks Lightsider.

The whole animation works perfectly except the timing. The animation start and ends correctly for the first time. However, the animation seems to be slower than it should be after repeating few times. i.e. There is a tick tock sound playing at the background which is verified to be accurate but the rotation animation lag behind after few repeats.

One of the reason is that some codes are running before repeating. May you suggest a way to do the animation using any solution which can be used in android to make an accurate pendulum? Thank you very much.

Community
  • 1
  • 1

1 Answers1

0

I solved by adding codes in animation listener. I logged the start time when animation starts. When repeating, I calculate the expected duration the animation should use. Compare it with the time used by the animation. Shorten the animation duration if the animation used more time than expected. Reset to defined duration if the animation uses less time than expected.

    needleDeflection.setAnimationListener(new AnimationListener() {
        @Override
        public void onAnimationStart(Animation arg0) {
            long startTime=AnimationUtils.currentAnimationTimeMillis();
            GaugeView.this.animateRepeatedTimes=0;
            GaugeView.this.animateStartTime = startTime;
        }

        @Override
        public void onAnimationRepeat(Animation arg0) {
            //duration of the animation * number of runs of animation + (long)starttime
            long expectedEndTime = GaugeView.this.deflectTime* ++GaugeView.this.animateRepeatedTimes + GaugeView.this.animateStartTime;
            long repeatTime=AnimationUtils.currentAnimationTimeMillis();
            long timeExcessUsed = repeatTime - expectedEndTime;
            if(timeExcessUsed>0){
                //reduce the animation duration
                arg0.setDuration(GaugeView.this.deflectTime-timeExcessUsed);
            }else{
                //reset the animation duration to normal
                arg0.setDuration(GaugeView.this.deflectTime);
            }
        }

        @Override
        public void onAnimationEnd(Animation arg0) {
            previousAngle = currentAngle;
        }
    });