Home | History | Annotate | Download | only in display
      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