/*
|
* 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.IntProperty;
|
import android.view.Choreographer;
|
|
/**
|
* 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 IntProperty<T> mProperty;
|
private final Choreographer mChoreographer;
|
|
private int mCurrentValue;
|
private int mTargetValue;
|
private int 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, IntProperty<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(int target, int 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 int oldCurrentValue = mCurrentValue;
|
mCurrentValue = Math.round(mAnimatedValue);
|
|
if (oldCurrentValue != mCurrentValue) {
|
mProperty.setValue(mObject, mCurrentValue);
|
}
|
|
if (mTargetValue != mCurrentValue) {
|
postAnimationCallback();
|
} else {
|
mAnimating = false;
|
if (mListener != null) {
|
mListener.onAnimationEnd();
|
}
|
}
|
}
|
};
|
|
public interface Listener {
|
void onAnimationEnd();
|
}
|
}
|