/* * 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.power; 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; 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, the value jumps * directly to the target. * * @param target The target value. * @param rate The convergence rate, in units per second. * @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) { mFirstTime = false; mProperty.setValue(mObject, target); mCurrentValue = target; return true; } // 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(); postCallback(); } return changed; } private void postCallback() { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null); } private final Runnable mCallback = new Runnable() { @Override // Choreographer callback public void run() { final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) * 0.000000001f; final float amount = timeDelta * mRate / ValueAnimator.getDurationScale(); 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. int oldCurrentValue = mCurrentValue; if (mTargetValue > mCurrentValue) { mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); } else { mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); } mCurrentValue = (int)Math.round(mAnimatedValue); if (oldCurrentValue != mCurrentValue) { mProperty.setValue(mObject, mCurrentValue); } if (mTargetValue != mCurrentValue) { postCallback(); } else { mAnimating = false; } } }; }