RampAnimator.java revision 4255869ac00e700395832ee73b2294603d4b6ece
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.display; 18 19import android.animation.ValueAnimator; 20import android.util.IntProperty; 21import android.view.Choreographer; 22 23/** 24 * A custom animator that progressively updates a property value at 25 * a given variable rate until it reaches a particular target value. 26 */ 27final class RampAnimator<T> { 28 private final T mObject; 29 private final IntProperty<T> mProperty; 30 private final Choreographer mChoreographer; 31 32 private int mCurrentValue; 33 private int mTargetValue; 34 private int mRate; 35 36 private boolean mAnimating; 37 private float mAnimatedValue; // higher precision copy of mCurrentValue 38 private long mLastFrameTimeNanos; 39 40 private boolean mFirstTime = true; 41 42 private Listener mListener; 43 44 public RampAnimator(T object, IntProperty<T> property) { 45 mObject = object; 46 mProperty = property; 47 mChoreographer = Choreographer.getInstance(); 48 } 49 50 /** 51 * Starts animating towards the specified value. 52 * 53 * If this is the first time the property is being set, the value jumps 54 * directly to the target. 55 * 56 * @param target The target value. 57 * @param rate The convergence rate, in units per second. 58 * @return True if the target differs from the previous target. 59 */ 60 public boolean animateTo(int target, int rate) { 61 // Immediately jump to the target the first time. 62 if (mFirstTime) { 63 mFirstTime = false; 64 mProperty.setValue(mObject, target); 65 mCurrentValue = target; 66 return true; 67 } 68 69 // Adjust the rate based on the closest target. 70 // If a faster rate is specified, then use the new rate so that we converge 71 // more rapidly based on the new request. 72 // If a slower rate is specified, then use the new rate only if the current 73 // value is somewhere in between the new and the old target meaning that 74 // we will be ramping in a different direction to get there. 75 // Otherwise, continue at the previous rate. 76 if (!mAnimating 77 || rate > mRate 78 || (target <= mCurrentValue && mCurrentValue <= mTargetValue) 79 || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { 80 mRate = rate; 81 } 82 83 final boolean changed = (mTargetValue != target); 84 mTargetValue = target; 85 86 // Start animating. 87 if (!mAnimating && target != mCurrentValue) { 88 mAnimating = true; 89 mAnimatedValue = mCurrentValue; 90 mLastFrameTimeNanos = System.nanoTime(); 91 postCallback(); 92 } 93 94 return changed; 95 } 96 97 /** 98 * Returns true if the animation is running. 99 */ 100 public boolean isAnimating() { 101 return mAnimating; 102 } 103 104 /** 105 * Sets a listener to watch for animation events. 106 */ 107 public void setListener(Listener listener) { 108 mListener = listener; 109 } 110 111 private void postCallback() { 112 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null); 113 } 114 115 private final Runnable mCallback = new Runnable() { 116 @Override // Choreographer callback 117 public void run() { 118 final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); 119 final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) 120 * 0.000000001f; 121 mLastFrameTimeNanos = frameTimeNanos; 122 123 // Advance the animated value towards the target at the specified rate 124 // and clamp to the target. This gives us the new current value but 125 // we keep the animated value around to allow for fractional increments 126 // towards the target. 127 final float scale = ValueAnimator.getDurationScale(); 128 if (scale == 0) { 129 // Animation off. 130 mAnimatedValue = mTargetValue; 131 } else { 132 final float amount = timeDelta * mRate / scale; 133 if (mTargetValue > mCurrentValue) { 134 mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); 135 } else { 136 mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); 137 } 138 } 139 final int oldCurrentValue = mCurrentValue; 140 mCurrentValue = Math.round(mAnimatedValue); 141 142 if (oldCurrentValue != mCurrentValue) { 143 mProperty.setValue(mObject, mCurrentValue); 144 } 145 146 if (mTargetValue != mCurrentValue) { 147 postCallback(); 148 } else { 149 mAnimating = false; 150 if (mListener != null) { 151 mListener.onAnimationEnd(); 152 } 153 } 154 } 155 }; 156 157 public interface Listener { 158 void onAnimationEnd(); 159 } 160} 161