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.power; 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 public RampAnimator(T object, IntProperty<T> property) { 43 mObject = object; 44 mProperty = property; 45 mChoreographer = Choreographer.getInstance(); 46 } 47 48 /** 49 * Starts animating towards the specified value. 50 * 51 * If this is the first time the property is being set, the value jumps 52 * directly to the target. 53 * 54 * @param target The target value. 55 * @param rate The convergence rate, in units per second. 56 * @return True if the target differs from the previous target. 57 */ 58 public boolean animateTo(int target, int rate) { 59 // Immediately jump to the target the first time. 60 if (mFirstTime) { 61 mFirstTime = false; 62 mProperty.setValue(mObject, target); 63 mCurrentValue = target; 64 return true; 65 } 66 67 // Adjust the rate based on the closest target. 68 // If a faster rate is specified, then use the new rate so that we converge 69 // more rapidly based on the new request. 70 // If a slower rate is specified, then use the new rate only if the current 71 // value is somewhere in between the new and the old target meaning that 72 // we will be ramping in a different direction to get there. 73 // Otherwise, continue at the previous rate. 74 if (!mAnimating 75 || rate > mRate 76 || (target <= mCurrentValue && mCurrentValue <= mTargetValue) 77 || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { 78 mRate = rate; 79 } 80 81 final boolean changed = (mTargetValue != target); 82 mTargetValue = target; 83 84 // Start animating. 85 if (!mAnimating && target != mCurrentValue) { 86 mAnimating = true; 87 mAnimatedValue = mCurrentValue; 88 mLastFrameTimeNanos = System.nanoTime(); 89 postCallback(); 90 } 91 92 return changed; 93 } 94 95 private void postCallback() { 96 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null); 97 } 98 99 private final Runnable mCallback = new Runnable() { 100 @Override // Choreographer callback 101 public void run() { 102 final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); 103 final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) 104 * 0.000000001f; 105 mLastFrameTimeNanos = frameTimeNanos; 106 107 // Advance the animated value towards the target at the specified rate 108 // and clamp to the target. This gives us the new current value but 109 // we keep the animated value around to allow for fractional increments 110 // towards the target. 111 final float scale = ValueAnimator.getDurationScale(); 112 if (scale == 0) { 113 // Animation off. 114 mAnimatedValue = mTargetValue; 115 } else { 116 final float amount = timeDelta * mRate / scale; 117 if (mTargetValue > mCurrentValue) { 118 mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); 119 } else { 120 mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); 121 } 122 } 123 final int oldCurrentValue = mCurrentValue; 124 mCurrentValue = Math.round(mAnimatedValue); 125 126 if (oldCurrentValue != mCurrentValue) { 127 mProperty.setValue(mObject, mCurrentValue); 128 } 129 130 if (mTargetValue != mCurrentValue) { 131 postCallback(); 132 } else { 133 mAnimating = false; 134 } 135 } 136 }; 137 } 138