blob: 7916d816dc9b7e4abd3c1f22a68618b5dfc1b8c4 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.display;
import android.animation.ValueAnimator;
import android.util.FloatProperty;
import android.view.Choreographer;
import com.android.internal.BrightnessSynchronizer;
/**
* A custom animator that progressively updates a property value at
* a given variable rate until it reaches a particular target value.
*/
final class RampAnimator<T> {
private final T mObject;
private final FloatProperty<T> mProperty;
private final Choreographer mChoreographer;
private float mCurrentValue;
private float mTargetValue;
private float mRate;
private boolean mAnimating;
private float mAnimatedValue; // higher precision copy of mCurrentValue
private long mLastFrameTimeNanos;
private boolean mFirstTime = true;
private Listener mListener;
public RampAnimator(T object, FloatProperty<T> property) {
mObject = object;
mProperty = property;
mChoreographer = Choreographer.getInstance();
}
/**
* Starts animating towards the specified value.
*
* If this is the first time the property is being set or if the rate is 0,
* the value jumps directly to the target.
*
* @param target The target value.
* @param rate The convergence rate in units per second, or 0 to set the value immediately.
* @return True if the target differs from the previous target.
*/
public boolean animateTo(float target, float rate) {
// Immediately jump to the target the first time.
if (mFirstTime || rate <= 0) {
if (mFirstTime || target != mCurrentValue) {
mFirstTime = false;
mRate = 0;
mTargetValue = target;
mCurrentValue = target;
mProperty.setValue(mObject, target);
if (mAnimating) {
mAnimating = false;
cancelAnimationCallback();
}
if (mListener != null) {
mListener.onAnimationEnd();
}
return true;
}
return false;
}
// Adjust the rate based on the closest target.
// If a faster rate is specified, then use the new rate so that we converge
// more rapidly based on the new request.
// If a slower rate is specified, then use the new rate only if the current
// value is somewhere in between the new and the old target meaning that
// we will be ramping in a different direction to get there.
// Otherwise, continue at the previous rate.
if (!mAnimating
|| rate > mRate
|| (target <= mCurrentValue && mCurrentValue <= mTargetValue)
|| (mTargetValue <= mCurrentValue && mCurrentValue <= target)) {
mRate = rate;
}
final boolean changed = (mTargetValue != target);
mTargetValue = target;
// Start animating.
if (!mAnimating && target != mCurrentValue) {
mAnimating = true;
mAnimatedValue = mCurrentValue;
mLastFrameTimeNanos = System.nanoTime();
postAnimationCallback();
}
return changed;
}
/**
* Returns true if the animation is running.
*/
public boolean isAnimating() {
return mAnimating;
}
/**
* Sets a listener to watch for animation events.
*/
public void setListener(Listener listener) {
mListener = listener;
}
private void postAnimationCallback() {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null);
}
private void cancelAnimationCallback() {
mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null);
}
private final Runnable mAnimationCallback = new Runnable() {
@Override // Choreographer callback
public void run() {
final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos)
* 0.000000001f;
mLastFrameTimeNanos = frameTimeNanos;
// Advance the animated value towards the target at the specified rate
// and clamp to the target. This gives us the new current value but
// we keep the animated value around to allow for fractional increments
// towards the target.
final float scale = ValueAnimator.getDurationScale();
if (scale == 0) {
// Animation off.
mAnimatedValue = mTargetValue;
} else {
final float amount = timeDelta * mRate / scale;
if (mTargetValue > mCurrentValue) {
mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue);
} else {
mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue);
}
}
final float oldCurrentValue = mCurrentValue;
mCurrentValue = mAnimatedValue;
if (!BrightnessSynchronizer.floatEquals(oldCurrentValue, mCurrentValue)) {
mProperty.setValue(mObject, mCurrentValue);
}
if (!BrightnessSynchronizer.floatEquals(mTargetValue, mCurrentValue)) {
postAnimationCallback();
} else {
mAnimating = false;
if (mListener != null) {
mListener.onAnimationEnd();
}
}
}
};
public interface Listener {
void onAnimationEnd();
}
}