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 17 package com.android.server.display; 18 19 import android.animation.ValueAnimator; 20 import android.util.IntProperty; 21 import 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 */ 27 final 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 or if the rate is 0, 54 * the value jumps directly to the target. 55 * 56 * @param target The target value. 57 * @param rate The convergence rate in units per second, or 0 to set the value immediately. 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 || rate <= 0) { 63 if (mFirstTime || target != mCurrentValue) { 64 mFirstTime = false; 65 mRate = 0; 66 mTargetValue = target; 67 mCurrentValue = target; 68 mProperty.setValue(mObject, target); 69 if (mAnimating) { 70 mAnimating = false; 71 cancelAnimationCallback(); 72 } 73 if (mListener != null) { 74 mListener.onAnimationEnd(); 75 } 76 return true; 77 } 78 return false; 79 } 80 81 // Adjust the rate based on the closest target. 82 // If a faster rate is specified, then use the new rate so that we converge 83 // more rapidly based on the new request. 84 // If a slower rate is specified, then use the new rate only if the current 85 // value is somewhere in between the new and the old target meaning that 86 // we will be ramping in a different direction to get there. 87 // Otherwise, continue at the previous rate. 88 if (!mAnimating 89 || rate > mRate 90 || (target <= mCurrentValue && mCurrentValue <= mTargetValue) 91 || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { 92 mRate = rate; 93 } 94 95 final boolean changed = (mTargetValue != target); 96 mTargetValue = target; 97 98 // Start animating. 99 if (!mAnimating && target != mCurrentValue) { 100 mAnimating = true; 101 mAnimatedValue = mCurrentValue; 102 mLastFrameTimeNanos = System.nanoTime(); 103 postAnimationCallback(); 104 } 105 106 return changed; 107 } 108 109 /** 110 * Returns true if the animation is running. 111 */ 112 public boolean isAnimating() { 113 return mAnimating; 114 } 115 116 /** 117 * Sets a listener to watch for animation events. 118 */ 119 public void setListener(Listener listener) { 120 mListener = listener; 121 } 122 123 private void postAnimationCallback() { 124 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); 125 } 126 127 private void cancelAnimationCallback() { 128 mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); 129 } 130 131 private final Runnable mAnimationCallback = new Runnable() { 132 @Override // Choreographer callback 133 public void run() { 134 final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); 135 final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) 136 * 0.000000001f; 137 mLastFrameTimeNanos = frameTimeNanos; 138 139 // Advance the animated value towards the target at the specified rate 140 // and clamp to the target. This gives us the new current value but 141 // we keep the animated value around to allow for fractional increments 142 // towards the target. 143 final float scale = ValueAnimator.getDurationScale(); 144 if (scale == 0) { 145 // Animation off. 146 mAnimatedValue = mTargetValue; 147 } else { 148 final float amount = timeDelta * mRate / scale; 149 if (mTargetValue > mCurrentValue) { 150 mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); 151 } else { 152 mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); 153 } 154 } 155 final int oldCurrentValue = mCurrentValue; 156 mCurrentValue = Math.round(mAnimatedValue); 157 158 if (oldCurrentValue != mCurrentValue) { 159 mProperty.setValue(mObject, mCurrentValue); 160 } 161 162 if (mTargetValue != mCurrentValue) { 163 postAnimationCallback(); 164 } else { 165 mAnimating = false; 166 if (mListener != null) { 167 mListener.onAnimationEnd(); 168 } 169 } 170 } 171 }; 172 173 public interface Listener { 174 void onAnimationEnd(); 175 } 176 } 177