Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2010 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 android.animation;
     18 
     19 import android.os.Looper;
     20 import android.os.Trace;
     21 import android.util.AndroidRuntimeException;
     22 import android.view.Choreographer;
     23 import android.view.animation.AccelerateDecelerateInterpolator;
     24 import android.view.animation.AnimationUtils;
     25 import android.view.animation.LinearInterpolator;
     26 
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 
     30 /**
     31  * This class provides a simple timing engine for running animations
     32  * which calculate animated values and set them on target objects.
     33  *
     34  * <p>There is a single timing pulse that all animations use. It runs in a
     35  * custom handler to ensure that property changes happen on the UI thread.</p>
     36  *
     37  * <p>By default, ValueAnimator uses non-linear time interpolation, via the
     38  * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
     39  * out of an animation. This behavior can be changed by calling
     40  * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
     41  *
     42  * <div class="special reference">
     43  * <h3>Developer Guides</h3>
     44  * <p>For more information about animating with {@code ValueAnimator}, read the
     45  * <a href="{@docRoot}guide/topics/graphics/prop-animation.html#value-animator">Property
     46  * Animation</a> developer guide.</p>
     47  * </div>
     48  */
     49 @SuppressWarnings("unchecked")
     50 public class ValueAnimator extends Animator {
     51 
     52     /**
     53      * Internal constants
     54      */
     55     private static float sDurationScale = 1.0f;
     56 
     57     /**
     58      * Values used with internal variable mPlayingState to indicate the current state of an
     59      * animation.
     60      */
     61     static final int STOPPED    = 0; // Not yet playing
     62     static final int RUNNING    = 1; // Playing normally
     63     static final int SEEKED     = 2; // Seeked to some time value
     64 
     65     /**
     66      * Internal variables
     67      * NOTE: This object implements the clone() method, making a deep copy of any referenced
     68      * objects. As other non-trivial fields are added to this class, make sure to add logic
     69      * to clone() to make deep copies of them.
     70      */
     71 
     72     // The first time that the animation's animateFrame() method is called. This time is used to
     73     // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
     74     // to animateFrame()
     75     long mStartTime;
     76 
     77     /**
     78      * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
     79      * to a value.
     80      */
     81     long mSeekTime = -1;
     82 
     83     // The static sAnimationHandler processes the internal timing loop on which all animations
     84     // are based
     85     /**
     86      * @hide
     87      */
     88     protected static ThreadLocal<AnimationHandler> sAnimationHandler =
     89             new ThreadLocal<AnimationHandler>();
     90 
     91     // The time interpolator to be used if none is set on the animation
     92     private static final TimeInterpolator sDefaultInterpolator =
     93             new AccelerateDecelerateInterpolator();
     94 
     95     /**
     96      * Used to indicate whether the animation is currently playing in reverse. This causes the
     97      * elapsed fraction to be inverted to calculate the appropriate values.
     98      */
     99     private boolean mPlayingBackwards = false;
    100 
    101     /**
    102      * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
    103      * repeatCount (if repeatCount!=INFINITE), the animation ends
    104      */
    105     private int mCurrentIteration = 0;
    106 
    107     /**
    108      * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
    109      */
    110     private float mCurrentFraction = 0f;
    111 
    112     /**
    113      * Tracks whether a startDelay'd animation has begun playing through the startDelay.
    114      */
    115     private boolean mStartedDelay = false;
    116 
    117     /**
    118      * Tracks the time at which the animation began playing through its startDelay. This is
    119      * different from the mStartTime variable, which is used to track when the animation became
    120      * active (which is when the startDelay expired and the animation was added to the active
    121      * animations list).
    122      */
    123     private long mDelayStartTime;
    124 
    125     /**
    126      * Flag that represents the current state of the animation. Used to figure out when to start
    127      * an animation (if state == STOPPED). Also used to end an animation that
    128      * has been cancel()'d or end()'d since the last animation frame. Possible values are
    129      * STOPPED, RUNNING, SEEKED.
    130      */
    131     int mPlayingState = STOPPED;
    132 
    133     /**
    134      * Additional playing state to indicate whether an animator has been start()'d. There is
    135      * some lag between a call to start() and the first animation frame. We should still note
    136      * that the animation has been started, even if it's first animation frame has not yet
    137      * happened, and reflect that state in isRunning().
    138      * Note that delayed animations are different: they are not started until their first
    139      * animation frame, which occurs after their delay elapses.
    140      */
    141     private boolean mRunning = false;
    142 
    143     /**
    144      * Additional playing state to indicate whether an animator has been start()'d, whether or
    145      * not there is a nonzero startDelay.
    146      */
    147     private boolean mStarted = false;
    148 
    149     /**
    150      * Tracks whether we've notified listeners of the onAnimationSTart() event. This can be
    151      * complex to keep track of since we notify listeners at different times depending on
    152      * startDelay and whether start() was called before end().
    153      */
    154     private boolean mStartListenersCalled = false;
    155 
    156     /**
    157      * Flag that denotes whether the animation is set up and ready to go. Used to
    158      * set up animation that has not yet been started.
    159      */
    160     boolean mInitialized = false;
    161 
    162     //
    163     // Backing variables
    164     //
    165 
    166     // How long the animation should last in ms
    167     private long mDuration = (long)(300 * sDurationScale);
    168     private long mUnscaledDuration = 300;
    169 
    170     // The amount of time in ms to delay starting the animation after start() is called
    171     private long mStartDelay = 0;
    172     private long mUnscaledStartDelay = 0;
    173 
    174     // The number of times the animation will repeat. The default is 0, which means the animation
    175     // will play only once
    176     private int mRepeatCount = 0;
    177 
    178     /**
    179      * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
    180      * animation will start from the beginning on every new cycle. REVERSE means the animation
    181      * will reverse directions on each iteration.
    182      */
    183     private int mRepeatMode = RESTART;
    184 
    185     /**
    186      * The time interpolator to be used. The elapsed fraction of the animation will be passed
    187      * through this interpolator to calculate the interpolated fraction, which is then used to
    188      * calculate the animated values.
    189      */
    190     private TimeInterpolator mInterpolator = sDefaultInterpolator;
    191 
    192     /**
    193      * The set of listeners to be sent events through the life of an animation.
    194      */
    195     private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
    196 
    197     /**
    198      * The property/value sets being animated.
    199      */
    200     PropertyValuesHolder[] mValues;
    201 
    202     /**
    203      * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
    204      * by property name during calls to getAnimatedValue(String).
    205      */
    206     HashMap<String, PropertyValuesHolder> mValuesMap;
    207 
    208     /**
    209      * Public constants
    210      */
    211 
    212     /**
    213      * When the animation reaches the end and <code>repeatCount</code> is INFINITE
    214      * or a positive value, the animation restarts from the beginning.
    215      */
    216     public static final int RESTART = 1;
    217     /**
    218      * When the animation reaches the end and <code>repeatCount</code> is INFINITE
    219      * or a positive value, the animation reverses direction on every iteration.
    220      */
    221     public static final int REVERSE = 2;
    222     /**
    223      * This value used used with the {@link #setRepeatCount(int)} property to repeat
    224      * the animation indefinitely.
    225      */
    226     public static final int INFINITE = -1;
    227 
    228 
    229     /**
    230      * @hide
    231      */
    232     public static void setDurationScale(float durationScale) {
    233         sDurationScale = durationScale;
    234     }
    235 
    236     /**
    237      * @hide
    238      */
    239     public static float getDurationScale() {
    240         return sDurationScale;
    241     }
    242 
    243     /**
    244      * Creates a new ValueAnimator object. This default constructor is primarily for
    245      * use internally; the factory methods which take parameters are more generally
    246      * useful.
    247      */
    248     public ValueAnimator() {
    249     }
    250 
    251     /**
    252      * Constructs and returns a ValueAnimator that animates between int values. A single
    253      * value implies that that value is the one being animated to. However, this is not typically
    254      * useful in a ValueAnimator object because there is no way for the object to determine the
    255      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    256      * from the target object and property being animated). Therefore, there should typically
    257      * be two or more values.
    258      *
    259      * @param values A set of values that the animation will animate between over time.
    260      * @return A ValueAnimator object that is set up to animate between the given values.
    261      */
    262     public static ValueAnimator ofInt(int... values) {
    263         ValueAnimator anim = new ValueAnimator();
    264         anim.setIntValues(values);
    265         return anim;
    266     }
    267 
    268     /**
    269      * Constructs and returns a ValueAnimator that animates between float values. A single
    270      * value implies that that value is the one being animated to. However, this is not typically
    271      * useful in a ValueAnimator object because there is no way for the object to determine the
    272      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    273      * from the target object and property being animated). Therefore, there should typically
    274      * be two or more values.
    275      *
    276      * @param values A set of values that the animation will animate between over time.
    277      * @return A ValueAnimator object that is set up to animate between the given values.
    278      */
    279     public static ValueAnimator ofFloat(float... values) {
    280         ValueAnimator anim = new ValueAnimator();
    281         anim.setFloatValues(values);
    282         return anim;
    283     }
    284 
    285     /**
    286      * Constructs and returns a ValueAnimator that animates between the values
    287      * specified in the PropertyValuesHolder objects.
    288      *
    289      * @param values A set of PropertyValuesHolder objects whose values will be animated
    290      * between over time.
    291      * @return A ValueAnimator object that is set up to animate between the given values.
    292      */
    293     public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
    294         ValueAnimator anim = new ValueAnimator();
    295         anim.setValues(values);
    296         return anim;
    297     }
    298     /**
    299      * Constructs and returns a ValueAnimator that animates between Object values. A single
    300      * value implies that that value is the one being animated to. However, this is not typically
    301      * useful in a ValueAnimator object because there is no way for the object to determine the
    302      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    303      * from the target object and property being animated). Therefore, there should typically
    304      * be two or more values.
    305      *
    306      * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this
    307      * factory method also takes a TypeEvaluator object that the ValueAnimator will use
    308      * to perform that interpolation.
    309      *
    310      * @param evaluator A TypeEvaluator that will be called on each animation frame to
    311      * provide the ncessry interpolation between the Object values to derive the animated
    312      * value.
    313      * @param values A set of values that the animation will animate between over time.
    314      * @return A ValueAnimator object that is set up to animate between the given values.
    315      */
    316     public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
    317         ValueAnimator anim = new ValueAnimator();
    318         anim.setObjectValues(values);
    319         anim.setEvaluator(evaluator);
    320         return anim;
    321     }
    322 
    323     /**
    324      * Sets int values that will be animated between. A single
    325      * value implies that that value is the one being animated to. However, this is not typically
    326      * useful in a ValueAnimator object because there is no way for the object to determine the
    327      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    328      * from the target object and property being animated). Therefore, there should typically
    329      * be two or more values.
    330      *
    331      * <p>If there are already multiple sets of values defined for this ValueAnimator via more
    332      * than one PropertyValuesHolder object, this method will set the values for the first
    333      * of those objects.</p>
    334      *
    335      * @param values A set of values that the animation will animate between over time.
    336      */
    337     public void setIntValues(int... values) {
    338         if (values == null || values.length == 0) {
    339             return;
    340         }
    341         if (mValues == null || mValues.length == 0) {
    342             setValues(PropertyValuesHolder.ofInt("", values));
    343         } else {
    344             PropertyValuesHolder valuesHolder = mValues[0];
    345             valuesHolder.setIntValues(values);
    346         }
    347         // New property/values/target should cause re-initialization prior to starting
    348         mInitialized = false;
    349     }
    350 
    351     /**
    352      * Sets float values that will be animated between. A single
    353      * value implies that that value is the one being animated to. However, this is not typically
    354      * useful in a ValueAnimator object because there is no way for the object to determine the
    355      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    356      * from the target object and property being animated). Therefore, there should typically
    357      * be two or more values.
    358      *
    359      * <p>If there are already multiple sets of values defined for this ValueAnimator via more
    360      * than one PropertyValuesHolder object, this method will set the values for the first
    361      * of those objects.</p>
    362      *
    363      * @param values A set of values that the animation will animate between over time.
    364      */
    365     public void setFloatValues(float... values) {
    366         if (values == null || values.length == 0) {
    367             return;
    368         }
    369         if (mValues == null || mValues.length == 0) {
    370             setValues(PropertyValuesHolder.ofFloat("", values));
    371         } else {
    372             PropertyValuesHolder valuesHolder = mValues[0];
    373             valuesHolder.setFloatValues(values);
    374         }
    375         // New property/values/target should cause re-initialization prior to starting
    376         mInitialized = false;
    377     }
    378 
    379     /**
    380      * Sets the values to animate between for this animation. A single
    381      * value implies that that value is the one being animated to. However, this is not typically
    382      * useful in a ValueAnimator object because there is no way for the object to determine the
    383      * starting value for the animation (unlike ObjectAnimator, which can derive that value
    384      * from the target object and property being animated). Therefore, there should typically
    385      * be two or more values.
    386      *
    387      * <p>If there are already multiple sets of values defined for this ValueAnimator via more
    388      * than one PropertyValuesHolder object, this method will set the values for the first
    389      * of those objects.</p>
    390      *
    391      * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate
    392      * between these value objects. ValueAnimator only knows how to interpolate between the
    393      * primitive types specified in the other setValues() methods.</p>
    394      *
    395      * @param values The set of values to animate between.
    396      */
    397     public void setObjectValues(Object... values) {
    398         if (values == null || values.length == 0) {
    399             return;
    400         }
    401         if (mValues == null || mValues.length == 0) {
    402             setValues(PropertyValuesHolder.ofObject("", null, values));
    403         } else {
    404             PropertyValuesHolder valuesHolder = mValues[0];
    405             valuesHolder.setObjectValues(values);
    406         }
    407         // New property/values/target should cause re-initialization prior to starting
    408         mInitialized = false;
    409     }
    410 
    411     /**
    412      * Sets the values, per property, being animated between. This function is called internally
    413      * by the constructors of ValueAnimator that take a list of values. But a ValueAnimator can
    414      * be constructed without values and this method can be called to set the values manually
    415      * instead.
    416      *
    417      * @param values The set of values, per property, being animated between.
    418      */
    419     public void setValues(PropertyValuesHolder... values) {
    420         int numValues = values.length;
    421         mValues = values;
    422         mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    423         for (int i = 0; i < numValues; ++i) {
    424             PropertyValuesHolder valuesHolder = values[i];
    425             mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    426         }
    427         // New property/values/target should cause re-initialization prior to starting
    428         mInitialized = false;
    429     }
    430 
    431     /**
    432      * Returns the values that this ValueAnimator animates between. These values are stored in
    433      * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
    434      * of value objects instead.
    435      *
    436      * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
    437      * values, per property, that define the animation.
    438      */
    439     public PropertyValuesHolder[] getValues() {
    440         return mValues;
    441     }
    442 
    443     /**
    444      * This function is called immediately before processing the first animation
    445      * frame of an animation. If there is a nonzero <code>startDelay</code>, the
    446      * function is called after that delay ends.
    447      * It takes care of the final initialization steps for the
    448      * animation.
    449      *
    450      *  <p>Overrides of this method should call the superclass method to ensure
    451      *  that internal mechanisms for the animation are set up correctly.</p>
    452      */
    453     void initAnimation() {
    454         if (!mInitialized) {
    455             int numValues = mValues.length;
    456             for (int i = 0; i < numValues; ++i) {
    457                 mValues[i].init();
    458             }
    459             mInitialized = true;
    460         }
    461     }
    462 
    463 
    464     /**
    465      * Sets the length of the animation. The default duration is 300 milliseconds.
    466      *
    467      * @param duration The length of the animation, in milliseconds. This value cannot
    468      * be negative.
    469      * @return ValueAnimator The object called with setDuration(). This return
    470      * value makes it easier to compose statements together that construct and then set the
    471      * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
    472      */
    473     public ValueAnimator setDuration(long duration) {
    474         if (duration < 0) {
    475             throw new IllegalArgumentException("Animators cannot have negative duration: " +
    476                     duration);
    477         }
    478         mUnscaledDuration = duration;
    479         mDuration = (long)(duration * sDurationScale);
    480         return this;
    481     }
    482 
    483     /**
    484      * Gets the length of the animation. The default duration is 300 milliseconds.
    485      *
    486      * @return The length of the animation, in milliseconds.
    487      */
    488     public long getDuration() {
    489         return mUnscaledDuration;
    490     }
    491 
    492     /**
    493      * Sets the position of the animation to the specified point in time. This time should
    494      * be between 0 and the total duration of the animation, including any repetition. If
    495      * the animation has not yet been started, then it will not advance forward after it is
    496      * set to this time; it will simply set the time to this value and perform any appropriate
    497      * actions based on that time. If the animation is already running, then setCurrentPlayTime()
    498      * will set the current playing time to this value and continue playing from that point.
    499      *
    500      * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
    501      */
    502     public void setCurrentPlayTime(long playTime) {
    503         initAnimation();
    504         long currentTime = AnimationUtils.currentAnimationTimeMillis();
    505         if (mPlayingState != RUNNING) {
    506             mSeekTime = playTime;
    507             mPlayingState = SEEKED;
    508         }
    509         mStartTime = currentTime - playTime;
    510         doAnimationFrame(currentTime);
    511     }
    512 
    513     /**
    514      * Gets the current position of the animation in time, which is equal to the current
    515      * time minus the time that the animation started. An animation that is not yet started will
    516      * return a value of zero.
    517      *
    518      * @return The current position in time of the animation.
    519      */
    520     public long getCurrentPlayTime() {
    521         if (!mInitialized || mPlayingState == STOPPED) {
    522             return 0;
    523         }
    524         return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
    525     }
    526 
    527     /**
    528      * This custom, static handler handles the timing pulse that is shared by
    529      * all active animations. This approach ensures that the setting of animation
    530      * values will happen on the UI thread and that all animations will share
    531      * the same times for calculating their values, which makes synchronizing
    532      * animations possible.
    533      *
    534      * The handler uses the Choreographer for executing periodic callbacks.
    535      *
    536      * @hide
    537      */
    538     @SuppressWarnings("unchecked")
    539     protected static class AnimationHandler implements Runnable {
    540         // The per-thread list of all active animations
    541         /** @hide */
    542         protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
    543 
    544         // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
    545         private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
    546 
    547         // The per-thread set of animations to be started on the next animation frame
    548         /** @hide */
    549         protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
    550 
    551         /**
    552          * Internal per-thread collections used to avoid set collisions as animations start and end
    553          * while being processed.
    554          * @hide
    555          */
    556         protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
    557         private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
    558         private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
    559 
    560         private final Choreographer mChoreographer;
    561         private boolean mAnimationScheduled;
    562 
    563         private AnimationHandler() {
    564             mChoreographer = Choreographer.getInstance();
    565         }
    566 
    567         /**
    568          * Start animating on the next frame.
    569          */
    570         public void start() {
    571             scheduleAnimation();
    572         }
    573 
    574         private void doAnimationFrame(long frameTime) {
    575             // mPendingAnimations holds any animations that have requested to be started
    576             // We're going to clear mPendingAnimations, but starting animation may
    577             // cause more to be added to the pending list (for example, if one animation
    578             // starting triggers another starting). So we loop until mPendingAnimations
    579             // is empty.
    580             while (mPendingAnimations.size() > 0) {
    581                 ArrayList<ValueAnimator> pendingCopy =
    582                         (ArrayList<ValueAnimator>) mPendingAnimations.clone();
    583                 mPendingAnimations.clear();
    584                 int count = pendingCopy.size();
    585                 for (int i = 0; i < count; ++i) {
    586                     ValueAnimator anim = pendingCopy.get(i);
    587                     // If the animation has a startDelay, place it on the delayed list
    588                     if (anim.mStartDelay == 0) {
    589                         anim.startAnimation(this);
    590                     } else {
    591                         mDelayedAnims.add(anim);
    592                     }
    593                 }
    594             }
    595             // Next, process animations currently sitting on the delayed queue, adding
    596             // them to the active animations if they are ready
    597             int numDelayedAnims = mDelayedAnims.size();
    598             for (int i = 0; i < numDelayedAnims; ++i) {
    599                 ValueAnimator anim = mDelayedAnims.get(i);
    600                 if (anim.delayedAnimationFrame(frameTime)) {
    601                     mReadyAnims.add(anim);
    602                 }
    603             }
    604             int numReadyAnims = mReadyAnims.size();
    605             if (numReadyAnims > 0) {
    606                 for (int i = 0; i < numReadyAnims; ++i) {
    607                     ValueAnimator anim = mReadyAnims.get(i);
    608                     anim.startAnimation(this);
    609                     anim.mRunning = true;
    610                     mDelayedAnims.remove(anim);
    611                 }
    612                 mReadyAnims.clear();
    613             }
    614 
    615             // Now process all active animations. The return value from animationFrame()
    616             // tells the handler whether it should now be ended
    617             int numAnims = mAnimations.size();
    618             for (int i = 0; i < numAnims; ++i) {
    619                 mTmpAnimations.add(mAnimations.get(i));
    620             }
    621             for (int i = 0; i < numAnims; ++i) {
    622                 ValueAnimator anim = mTmpAnimations.get(i);
    623                 if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
    624                     mEndingAnims.add(anim);
    625                 }
    626             }
    627             mTmpAnimations.clear();
    628             if (mEndingAnims.size() > 0) {
    629                 for (int i = 0; i < mEndingAnims.size(); ++i) {
    630                     mEndingAnims.get(i).endAnimation(this);
    631                 }
    632                 mEndingAnims.clear();
    633             }
    634 
    635             // If there are still active or delayed animations, schedule a future call to
    636             // onAnimate to process the next frame of the animations.
    637             if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
    638                 scheduleAnimation();
    639             }
    640         }
    641 
    642         // Called by the Choreographer.
    643         @Override
    644         public void run() {
    645             mAnimationScheduled = false;
    646             doAnimationFrame(mChoreographer.getFrameTime());
    647         }
    648 
    649         private void scheduleAnimation() {
    650             if (!mAnimationScheduled) {
    651                 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
    652                 mAnimationScheduled = true;
    653             }
    654         }
    655     }
    656 
    657     /**
    658      * The amount of time, in milliseconds, to delay starting the animation after
    659      * {@link #start()} is called.
    660      *
    661      * @return the number of milliseconds to delay running the animation
    662      */
    663     public long getStartDelay() {
    664         return mUnscaledStartDelay;
    665     }
    666 
    667     /**
    668      * The amount of time, in milliseconds, to delay starting the animation after
    669      * {@link #start()} is called.
    670 
    671      * @param startDelay The amount of the delay, in milliseconds
    672      */
    673     public void setStartDelay(long startDelay) {
    674         this.mStartDelay = (long)(startDelay * sDurationScale);
    675         mUnscaledStartDelay = startDelay;
    676     }
    677 
    678     /**
    679      * The amount of time, in milliseconds, between each frame of the animation. This is a
    680      * requested time that the animation will attempt to honor, but the actual delay between
    681      * frames may be different, depending on system load and capabilities. This is a static
    682      * function because the same delay will be applied to all animations, since they are all
    683      * run off of a single timing loop.
    684      *
    685      * The frame delay may be ignored when the animation system uses an external timing
    686      * source, such as the display refresh rate (vsync), to govern animations.
    687      *
    688      * @return the requested time between frames, in milliseconds
    689      */
    690     public static long getFrameDelay() {
    691         return Choreographer.getFrameDelay();
    692     }
    693 
    694     /**
    695      * The amount of time, in milliseconds, between each frame of the animation. This is a
    696      * requested time that the animation will attempt to honor, but the actual delay between
    697      * frames may be different, depending on system load and capabilities. This is a static
    698      * function because the same delay will be applied to all animations, since they are all
    699      * run off of a single timing loop.
    700      *
    701      * The frame delay may be ignored when the animation system uses an external timing
    702      * source, such as the display refresh rate (vsync), to govern animations.
    703      *
    704      * @param frameDelay the requested time between frames, in milliseconds
    705      */
    706     public static void setFrameDelay(long frameDelay) {
    707         Choreographer.setFrameDelay(frameDelay);
    708     }
    709 
    710     /**
    711      * The most recent value calculated by this <code>ValueAnimator</code> when there is just one
    712      * property being animated. This value is only sensible while the animation is running. The main
    713      * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code>
    714      * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
    715      * is called during each animation frame, immediately after the value is calculated.
    716      *
    717      * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
    718      * the single property being animated. If there are several properties being animated
    719      * (specified by several PropertyValuesHolder objects in the constructor), this function
    720      * returns the animated value for the first of those objects.
    721      */
    722     public Object getAnimatedValue() {
    723         if (mValues != null && mValues.length > 0) {
    724             return mValues[0].getAnimatedValue();
    725         }
    726         // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
    727         return null;
    728     }
    729 
    730     /**
    731      * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
    732      * The main purpose for this read-only property is to retrieve the value from the
    733      * <code>ValueAnimator</code> during a call to
    734      * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
    735      * is called during each animation frame, immediately after the value is calculated.
    736      *
    737      * @return animatedValue The value most recently calculated for the named property
    738      * by this <code>ValueAnimator</code>.
    739      */
    740     public Object getAnimatedValue(String propertyName) {
    741         PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
    742         if (valuesHolder != null) {
    743             return valuesHolder.getAnimatedValue();
    744         } else {
    745             // At least avoid crashing if called with bogus propertyName
    746             return null;
    747         }
    748     }
    749 
    750     /**
    751      * Sets how many times the animation should be repeated. If the repeat
    752      * count is 0, the animation is never repeated. If the repeat count is
    753      * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
    754      * into account. The repeat count is 0 by default.
    755      *
    756      * @param value the number of times the animation should be repeated
    757      */
    758     public void setRepeatCount(int value) {
    759         mRepeatCount = value;
    760     }
    761     /**
    762      * Defines how many times the animation should repeat. The default value
    763      * is 0.
    764      *
    765      * @return the number of times the animation should repeat, or {@link #INFINITE}
    766      */
    767     public int getRepeatCount() {
    768         return mRepeatCount;
    769     }
    770 
    771     /**
    772      * Defines what this animation should do when it reaches the end. This
    773      * setting is applied only when the repeat count is either greater than
    774      * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
    775      *
    776      * @param value {@link #RESTART} or {@link #REVERSE}
    777      */
    778     public void setRepeatMode(int value) {
    779         mRepeatMode = value;
    780     }
    781 
    782     /**
    783      * Defines what this animation should do when it reaches the end.
    784      *
    785      * @return either one of {@link #REVERSE} or {@link #RESTART}
    786      */
    787     public int getRepeatMode() {
    788         return mRepeatMode;
    789     }
    790 
    791     /**
    792      * Adds a listener to the set of listeners that are sent update events through the life of
    793      * an animation. This method is called on all listeners for every frame of the animation,
    794      * after the values for the animation have been calculated.
    795      *
    796      * @param listener the listener to be added to the current set of listeners for this animation.
    797      */
    798     public void addUpdateListener(AnimatorUpdateListener listener) {
    799         if (mUpdateListeners == null) {
    800             mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
    801         }
    802         mUpdateListeners.add(listener);
    803     }
    804 
    805     /**
    806      * Removes all listeners from the set listening to frame updates for this animation.
    807      */
    808     public void removeAllUpdateListeners() {
    809         if (mUpdateListeners == null) {
    810             return;
    811         }
    812         mUpdateListeners.clear();
    813         mUpdateListeners = null;
    814     }
    815 
    816     /**
    817      * Removes a listener from the set listening to frame updates for this animation.
    818      *
    819      * @param listener the listener to be removed from the current set of update listeners
    820      * for this animation.
    821      */
    822     public void removeUpdateListener(AnimatorUpdateListener listener) {
    823         if (mUpdateListeners == null) {
    824             return;
    825         }
    826         mUpdateListeners.remove(listener);
    827         if (mUpdateListeners.size() == 0) {
    828             mUpdateListeners = null;
    829         }
    830     }
    831 
    832 
    833     /**
    834      * The time interpolator used in calculating the elapsed fraction of this animation. The
    835      * interpolator determines whether the animation runs with linear or non-linear motion,
    836      * such as acceleration and deceleration. The default value is
    837      * {@link android.view.animation.AccelerateDecelerateInterpolator}
    838      *
    839      * @param value the interpolator to be used by this animation. A value of <code>null</code>
    840      * will result in linear interpolation.
    841      */
    842     @Override
    843     public void setInterpolator(TimeInterpolator value) {
    844         if (value != null) {
    845             mInterpolator = value;
    846         } else {
    847             mInterpolator = new LinearInterpolator();
    848         }
    849     }
    850 
    851     /**
    852      * Returns the timing interpolator that this ValueAnimator uses.
    853      *
    854      * @return The timing interpolator for this ValueAnimator.
    855      */
    856     @Override
    857     public TimeInterpolator getInterpolator() {
    858         return mInterpolator;
    859     }
    860 
    861     /**
    862      * The type evaluator to be used when calculating the animated values of this animation.
    863      * The system will automatically assign a float or int evaluator based on the type
    864      * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
    865      * are not one of these primitive types, or if different evaluation is desired (such as is
    866      * necessary with int values that represent colors), a custom evaluator needs to be assigned.
    867      * For example, when running an animation on color values, the {@link ArgbEvaluator}
    868      * should be used to get correct RGB color interpolation.
    869      *
    870      * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
    871      * will be used for that set. If there are several sets of values being animated, which is
    872      * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator
    873      * is assigned just to the first PropertyValuesHolder object.</p>
    874      *
    875      * @param value the evaluator to be used this animation
    876      */
    877     public void setEvaluator(TypeEvaluator value) {
    878         if (value != null && mValues != null && mValues.length > 0) {
    879             mValues[0].setEvaluator(value);
    880         }
    881     }
    882 
    883     private void notifyStartListeners() {
    884         if (mListeners != null && !mStartListenersCalled) {
    885             ArrayList<AnimatorListener> tmpListeners =
    886                     (ArrayList<AnimatorListener>) mListeners.clone();
    887             int numListeners = tmpListeners.size();
    888             for (int i = 0; i < numListeners; ++i) {
    889                 tmpListeners.get(i).onAnimationStart(this);
    890             }
    891         }
    892         mStartListenersCalled = true;
    893     }
    894 
    895     /**
    896      * Start the animation playing. This version of start() takes a boolean flag that indicates
    897      * whether the animation should play in reverse. The flag is usually false, but may be set
    898      * to true if called from the reverse() method.
    899      *
    900      * <p>The animation started by calling this method will be run on the thread that called
    901      * this method. This thread should have a Looper on it (a runtime exception will be thrown if
    902      * this is not the case). Also, if the animation will animate
    903      * properties of objects in the view hierarchy, then the calling thread should be the UI
    904      * thread for that view hierarchy.</p>
    905      *
    906      * @param playBackwards Whether the ValueAnimator should start playing in reverse.
    907      */
    908     private void start(boolean playBackwards) {
    909         if (Looper.myLooper() == null) {
    910             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    911         }
    912         mPlayingBackwards = playBackwards;
    913         mCurrentIteration = 0;
    914         mPlayingState = STOPPED;
    915         mStarted = true;
    916         mStartedDelay = false;
    917         AnimationHandler animationHandler = getOrCreateAnimationHandler();
    918         animationHandler.mPendingAnimations.add(this);
    919         if (mStartDelay == 0) {
    920             // This sets the initial value of the animation, prior to actually starting it running
    921             setCurrentPlayTime(0);
    922             mPlayingState = STOPPED;
    923             mRunning = true;
    924             notifyStartListeners();
    925         }
    926         animationHandler.start();
    927     }
    928 
    929     @Override
    930     public void start() {
    931         start(false);
    932     }
    933 
    934     @Override
    935     public void cancel() {
    936         // Only cancel if the animation is actually running or has been started and is about
    937         // to run
    938         AnimationHandler handler = getOrCreateAnimationHandler();
    939         if (mPlayingState != STOPPED
    940                 || handler.mPendingAnimations.contains(this)
    941                 || handler.mDelayedAnims.contains(this)) {
    942             // Only notify listeners if the animator has actually started
    943             if ((mStarted || mRunning) && mListeners != null) {
    944                 if (!mRunning) {
    945                     // If it's not yet running, then start listeners weren't called. Call them now.
    946                     notifyStartListeners();
    947                 }
    948                 ArrayList<AnimatorListener> tmpListeners =
    949                         (ArrayList<AnimatorListener>) mListeners.clone();
    950                 for (AnimatorListener listener : tmpListeners) {
    951                     listener.onAnimationCancel(this);
    952                 }
    953             }
    954             endAnimation(handler);
    955         }
    956     }
    957 
    958     @Override
    959     public void end() {
    960         AnimationHandler handler = getOrCreateAnimationHandler();
    961         if (!handler.mAnimations.contains(this) && !handler.mPendingAnimations.contains(this)) {
    962             // Special case if the animation has not yet started; get it ready for ending
    963             mStartedDelay = false;
    964             startAnimation(handler);
    965             mStarted = true;
    966         } else if (!mInitialized) {
    967             initAnimation();
    968         }
    969         animateValue(mPlayingBackwards ? 0f : 1f);
    970         endAnimation(handler);
    971     }
    972 
    973     @Override
    974     public boolean isRunning() {
    975         return (mPlayingState == RUNNING || mRunning);
    976     }
    977 
    978     @Override
    979     public boolean isStarted() {
    980         return mStarted;
    981     }
    982 
    983     /**
    984      * Plays the ValueAnimator in reverse. If the animation is already running,
    985      * it will stop itself and play backwards from the point reached when reverse was called.
    986      * If the animation is not currently running, then it will start from the end and
    987      * play backwards. This behavior is only set for the current animation; future playing
    988      * of the animation will use the default behavior of playing forward.
    989      */
    990     public void reverse() {
    991         mPlayingBackwards = !mPlayingBackwards;
    992         if (mPlayingState == RUNNING) {
    993             long currentTime = AnimationUtils.currentAnimationTimeMillis();
    994             long currentPlayTime = currentTime - mStartTime;
    995             long timeLeft = mDuration - currentPlayTime;
    996             mStartTime = currentTime - timeLeft;
    997         } else {
    998             start(true);
    999         }
   1000     }
   1001 
   1002     /**
   1003      * Called internally to end an animation by removing it from the animations list. Must be
   1004      * called on the UI thread.
   1005      */
   1006     private void endAnimation(AnimationHandler handler) {
   1007         handler.mAnimations.remove(this);
   1008         handler.mPendingAnimations.remove(this);
   1009         handler.mDelayedAnims.remove(this);
   1010         mPlayingState = STOPPED;
   1011         if ((mStarted || mRunning) && mListeners != null) {
   1012             if (!mRunning) {
   1013                 // If it's not yet running, then start listeners weren't called. Call them now.
   1014                 notifyStartListeners();
   1015              }
   1016             ArrayList<AnimatorListener> tmpListeners =
   1017                     (ArrayList<AnimatorListener>) mListeners.clone();
   1018             int numListeners = tmpListeners.size();
   1019             for (int i = 0; i < numListeners; ++i) {
   1020                 tmpListeners.get(i).onAnimationEnd(this);
   1021             }
   1022         }
   1023         mRunning = false;
   1024         mStarted = false;
   1025         mStartListenersCalled = false;
   1026         mPlayingBackwards = false;
   1027         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "animator",
   1028                 System.identityHashCode(this));
   1029     }
   1030 
   1031     /**
   1032      * Called internally to start an animation by adding it to the active animations list. Must be
   1033      * called on the UI thread.
   1034      */
   1035     private void startAnimation(AnimationHandler handler) {
   1036         Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "animator",
   1037                 System.identityHashCode(this));
   1038         initAnimation();
   1039         handler.mAnimations.add(this);
   1040         if (mStartDelay > 0 && mListeners != null) {
   1041             // Listeners were already notified in start() if startDelay is 0; this is
   1042             // just for delayed animations
   1043             notifyStartListeners();
   1044         }
   1045     }
   1046 
   1047     /**
   1048      * Internal function called to process an animation frame on an animation that is currently
   1049      * sleeping through its <code>startDelay</code> phase. The return value indicates whether it
   1050      * should be woken up and put on the active animations queue.
   1051      *
   1052      * @param currentTime The current animation time, used to calculate whether the animation
   1053      * has exceeded its <code>startDelay</code> and should be started.
   1054      * @return True if the animation's <code>startDelay</code> has been exceeded and the animation
   1055      * should be added to the set of active animations.
   1056      */
   1057     private boolean delayedAnimationFrame(long currentTime) {
   1058         if (!mStartedDelay) {
   1059             mStartedDelay = true;
   1060             mDelayStartTime = currentTime;
   1061         } else {
   1062             long deltaTime = currentTime - mDelayStartTime;
   1063             if (deltaTime > mStartDelay) {
   1064                 // startDelay ended - start the anim and record the
   1065                 // mStartTime appropriately
   1066                 mStartTime = currentTime - (deltaTime - mStartDelay);
   1067                 mPlayingState = RUNNING;
   1068                 return true;
   1069             }
   1070         }
   1071         return false;
   1072     }
   1073 
   1074     /**
   1075      * This internal function processes a single animation frame for a given animation. The
   1076      * currentTime parameter is the timing pulse sent by the handler, used to calculate the
   1077      * elapsed duration, and therefore
   1078      * the elapsed fraction, of the animation. The return value indicates whether the animation
   1079      * should be ended (which happens when the elapsed time of the animation exceeds the
   1080      * animation's duration, including the repeatCount).
   1081      *
   1082      * @param currentTime The current time, as tracked by the static timing handler
   1083      * @return true if the animation's duration, including any repetitions due to
   1084      * <code>repeatCount</code> has been exceeded and the animation should be ended.
   1085      */
   1086     boolean animationFrame(long currentTime) {
   1087         boolean done = false;
   1088         switch (mPlayingState) {
   1089         case RUNNING:
   1090         case SEEKED:
   1091             float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
   1092             if (fraction >= 1f) {
   1093                 if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
   1094                     // Time to repeat
   1095                     if (mListeners != null) {
   1096                         int numListeners = mListeners.size();
   1097                         for (int i = 0; i < numListeners; ++i) {
   1098                             mListeners.get(i).onAnimationRepeat(this);
   1099                         }
   1100                     }
   1101                     if (mRepeatMode == REVERSE) {
   1102                         mPlayingBackwards = !mPlayingBackwards;
   1103                     }
   1104                     mCurrentIteration += (int)fraction;
   1105                     fraction = fraction % 1f;
   1106                     mStartTime += mDuration;
   1107                 } else {
   1108                     done = true;
   1109                     fraction = Math.min(fraction, 1.0f);
   1110                 }
   1111             }
   1112             if (mPlayingBackwards) {
   1113                 fraction = 1f - fraction;
   1114             }
   1115             animateValue(fraction);
   1116             break;
   1117         }
   1118 
   1119         return done;
   1120     }
   1121 
   1122     /**
   1123      * Processes a frame of the animation, adjusting the start time if needed.
   1124      *
   1125      * @param frameTime The frame time.
   1126      * @return true if the animation has ended.
   1127      */
   1128     final boolean doAnimationFrame(long frameTime) {
   1129         if (mPlayingState == STOPPED) {
   1130             mPlayingState = RUNNING;
   1131             if (mSeekTime < 0) {
   1132                 mStartTime = frameTime;
   1133             } else {
   1134                 mStartTime = frameTime - mSeekTime;
   1135                 // Now that we're playing, reset the seek time
   1136                 mSeekTime = -1;
   1137             }
   1138         }
   1139         // The frame time might be before the start time during the first frame of
   1140         // an animation.  The "current time" must always be on or after the start
   1141         // time to avoid animating frames at negative time intervals.  In practice, this
   1142         // is very rare and only happens when seeking backwards.
   1143         final long currentTime = Math.max(frameTime, mStartTime);
   1144         return animationFrame(currentTime);
   1145     }
   1146 
   1147     /**
   1148      * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
   1149      * the most recent frame update on the animation.
   1150      *
   1151      * @return Elapsed/interpolated fraction of the animation.
   1152      */
   1153     public float getAnimatedFraction() {
   1154         return mCurrentFraction;
   1155     }
   1156 
   1157     /**
   1158      * This method is called with the elapsed fraction of the animation during every
   1159      * animation frame. This function turns the elapsed fraction into an interpolated fraction
   1160      * and then into an animated value (from the evaluator. The function is called mostly during
   1161      * animation updates, but it is also called when the <code>end()</code>
   1162      * function is called, to set the final value on the property.
   1163      *
   1164      * <p>Overrides of this method must call the superclass to perform the calculation
   1165      * of the animated value.</p>
   1166      *
   1167      * @param fraction The elapsed fraction of the animation.
   1168      */
   1169     void animateValue(float fraction) {
   1170         fraction = mInterpolator.getInterpolation(fraction);
   1171         mCurrentFraction = fraction;
   1172         int numValues = mValues.length;
   1173         for (int i = 0; i < numValues; ++i) {
   1174             mValues[i].calculateValue(fraction);
   1175         }
   1176         if (mUpdateListeners != null) {
   1177             int numListeners = mUpdateListeners.size();
   1178             for (int i = 0; i < numListeners; ++i) {
   1179                 mUpdateListeners.get(i).onAnimationUpdate(this);
   1180             }
   1181         }
   1182     }
   1183 
   1184     @Override
   1185     public ValueAnimator clone() {
   1186         final ValueAnimator anim = (ValueAnimator) super.clone();
   1187         if (mUpdateListeners != null) {
   1188             ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
   1189             anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
   1190             int numListeners = oldListeners.size();
   1191             for (int i = 0; i < numListeners; ++i) {
   1192                 anim.mUpdateListeners.add(oldListeners.get(i));
   1193             }
   1194         }
   1195         anim.mSeekTime = -1;
   1196         anim.mPlayingBackwards = false;
   1197         anim.mCurrentIteration = 0;
   1198         anim.mInitialized = false;
   1199         anim.mPlayingState = STOPPED;
   1200         anim.mStartedDelay = false;
   1201         PropertyValuesHolder[] oldValues = mValues;
   1202         if (oldValues != null) {
   1203             int numValues = oldValues.length;
   1204             anim.mValues = new PropertyValuesHolder[numValues];
   1205             anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
   1206             for (int i = 0; i < numValues; ++i) {
   1207                 PropertyValuesHolder newValuesHolder = oldValues[i].clone();
   1208                 anim.mValues[i] = newValuesHolder;
   1209                 anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
   1210             }
   1211         }
   1212         return anim;
   1213     }
   1214 
   1215     /**
   1216      * Implementors of this interface can add themselves as update listeners
   1217      * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
   1218      * frame, after the current frame's values have been calculated for that
   1219      * <code>ValueAnimator</code>.
   1220      */
   1221     public static interface AnimatorUpdateListener {
   1222         /**
   1223          * <p>Notifies the occurrence of another frame of the animation.</p>
   1224          *
   1225          * @param animation The animation which was repeated.
   1226          */
   1227         void onAnimationUpdate(ValueAnimator animation);
   1228 
   1229     }
   1230 
   1231     /**
   1232      * Return the number of animations currently running.
   1233      *
   1234      * Used by StrictMode internally to annotate violations.
   1235      * May be called on arbitrary threads!
   1236      *
   1237      * @hide
   1238      */
   1239     public static int getCurrentAnimationsCount() {
   1240         AnimationHandler handler = sAnimationHandler.get();
   1241         return handler != null ? handler.mAnimations.size() : 0;
   1242     }
   1243 
   1244     /**
   1245      * Clear all animations on this thread, without canceling or ending them.
   1246      * This should be used with caution.
   1247      *
   1248      * @hide
   1249      */
   1250     public static void clearAllAnimations() {
   1251         AnimationHandler handler = sAnimationHandler.get();
   1252         if (handler != null) {
   1253             handler.mAnimations.clear();
   1254             handler.mPendingAnimations.clear();
   1255             handler.mDelayedAnims.clear();
   1256         }
   1257     }
   1258 
   1259     private static AnimationHandler getOrCreateAnimationHandler() {
   1260         AnimationHandler handler = sAnimationHandler.get();
   1261         if (handler == null) {
   1262             handler = new AnimationHandler();
   1263             sAnimationHandler.set(handler);
   1264         }
   1265         return handler;
   1266     }
   1267 
   1268     @Override
   1269     public String toString() {
   1270         String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode());
   1271         if (mValues != null) {
   1272             for (int i = 0; i < mValues.length; ++i) {
   1273                 returnVal += "\n    " + mValues[i].toString();
   1274             }
   1275         }
   1276         return returnVal;
   1277     }
   1278 }
   1279