/* * 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 { private final T mObject; private final IntProperty 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 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(); } }