ViewPropertyAnimator.java revision 9d1992deaeb3d60d5928f05b649a2cc654ba98a3
1/*
2 * Copyright (C) 2011 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 android.view;
18
19import android.animation.Animator;
20import android.animation.ValueAnimator;
21import android.animation.TimeInterpolator;
22
23import java.util.ArrayList;
24import java.util.HashMap;
25import java.util.Set;
26
27/**
28 * This class enables automatic and optimized animation of select properties on View objects.
29 * If only one or two properties on a View object are being animated, then using an
30 * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator
31 * are well equipped to do the right thing to set the property and invalidate the view
32 * appropriately. But if several properties are animated simultaneously, or if you just want a
33 * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be
34 * more well-suited to the task.
35 *
36 * <p>This class may provide better performance for several simultaneous animations, because
37 * it will optimize invalidate calls to take place only once for several properties instead of each
38 * animated property independently causing its own invalidation. Also, the syntax of using this
39 * class could be easier to use because the caller need only tell the View object which
40 * property to animate, and the value to animate either to or by, and this class handles the
41 * details of configuring the underlying Animator class and starting it.</p>
42 *
43 * <p>This class is not constructed by the caller, but rather by the View whose properties
44 * it will animate. Calls to {@link android.view.View#animate()} will return a reference
45 * to the appropriate ViewPropertyAnimator object for that View.</p>
46 *
47 */
48public class ViewPropertyAnimator {
49
50    /**
51     * The View whose properties are being animated by this class. This is set at
52     * construction time.
53     */
54    private final View mView;
55
56    /**
57     * The duration of the underlying Animator object. By default, we don't set the duration
58     * on the Animator and just use its default duration. If the duration is ever set on this
59     * Animator, then we use the duration that it was set to.
60     */
61    private long mDuration;
62
63    /**
64     * A flag indicating whether the duration has been set on this object. If not, we don't set
65     * the duration on the underlying Animator, but instead just use its default duration.
66     */
67    private boolean mDurationSet = false;
68
69    /**
70     * The startDelay of the underlying Animator object. By default, we don't set the startDelay
71     * on the Animator and just use its default startDelay. If the startDelay is ever set on this
72     * Animator, then we use the startDelay that it was set to.
73     */
74    private long mStartDelay = 0;
75
76    /**
77     * A flag indicating whether the startDelay has been set on this object. If not, we don't set
78     * the startDelay on the underlying Animator, but instead just use its default startDelay.
79     */
80    private boolean mStartDelaySet = false;
81
82    /**
83     * The interpolator of the underlying Animator object. By default, we don't set the interpolator
84     * on the Animator and just use its default interpolator. If the interpolator is ever set on
85     * this Animator, then we use the interpolator that it was set to.
86     */
87    private TimeInterpolator mInterpolator;
88
89    /**
90     * A flag indicating whether the interpolator has been set on this object. If not, we don't set
91     * the interpolator on the underlying Animator, but instead just use its default interpolator.
92     */
93    private boolean mInterpolatorSet = false;
94
95    /**
96     * Listener for the lifecycle events of the underlying
97     */
98    private Animator.AnimatorListener mListener = null;
99
100    /**
101     * This listener is the mechanism by which the underlying Animator causes changes to the
102     * properties currently being animated, as well as the cleanup after an animation is
103     * complete.
104     */
105    private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
106
107    /**
108     * This list holds the properties that have been asked to animate. We allow the caller to
109     * request several animations prior to actually starting the underlying animator. This
110     * enables us to run one single animator to handle several properties in parallel. Each
111     * property is tossed onto the pending list until the animation actually starts (which is
112     * done by posting it onto mView), at which time the pending list is cleared and the properties
113     * on that list are added to the list of properties associated with that animator.
114     */
115    ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
116    private Runnable mPendingSetupAction;
117    private Runnable mPendingCleanupAction;
118    private Runnable mPendingOnStartAction;
119    private Runnable mPendingOnEndAction;
120
121    /**
122     * Constants used to associate a property being requested and the mechanism used to set
123     * the property (this class calls directly into View to set the properties in question).
124     */
125    private static final int NONE           = 0x0000;
126    private static final int TRANSLATION_X  = 0x0001;
127    private static final int TRANSLATION_Y  = 0x0002;
128    private static final int SCALE_X        = 0x0004;
129    private static final int SCALE_Y        = 0x0008;
130    private static final int ROTATION       = 0x0010;
131    private static final int ROTATION_X     = 0x0020;
132    private static final int ROTATION_Y     = 0x0040;
133    private static final int X              = 0x0080;
134    private static final int Y              = 0x0100;
135    private static final int ALPHA          = 0x0200;
136
137    private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y |
138            ROTATION | ROTATION_X | ROTATION_Y | X | Y;
139
140    /**
141     * The mechanism by which the user can request several properties that are then animated
142     * together works by posting this Runnable to start the underlying Animator. Every time
143     * a property animation is requested, we cancel any previous postings of the Runnable
144     * and re-post it. This means that we will only ever run the Runnable (and thus start the
145     * underlying animator) after the caller is done setting the properties that should be
146     * animated together.
147     */
148    private Runnable mAnimationStarter = new Runnable() {
149        @Override
150        public void run() {
151            startAnimation();
152        }
153    };
154
155    /**
156     * This class holds information about the overall animation being run on the set of
157     * properties. The mask describes which properties are being animated and the
158     * values holder is the list of all property/value objects.
159     */
160    private static class PropertyBundle {
161        int mPropertyMask;
162        ArrayList<NameValuesHolder> mNameValuesHolder;
163
164        PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
165            mPropertyMask = propertyMask;
166            mNameValuesHolder = nameValuesHolder;
167        }
168
169        /**
170         * Removes the given property from being animated as a part of this
171         * PropertyBundle. If the property was a part of this bundle, it returns
172         * true to indicate that it was, in fact, canceled. This is an indication
173         * to the caller that a cancellation actually occurred.
174         *
175         * @param propertyConstant The property whose cancellation is requested.
176         * @return true if the given property is a part of this bundle and if it
177         * has therefore been canceled.
178         */
179        boolean cancel(int propertyConstant) {
180            if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
181                int count = mNameValuesHolder.size();
182                for (int i = 0; i < count; ++i) {
183                    NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
184                    if (nameValuesHolder.mNameConstant == propertyConstant) {
185                        mNameValuesHolder.remove(i);
186                        mPropertyMask &= ~propertyConstant;
187                        return true;
188                    }
189                }
190            }
191            return false;
192        }
193    }
194
195    /**
196     * This list tracks the list of properties being animated by any particular animator.
197     * In most situations, there would only ever be one animator running at a time. But it is
198     * possible to request some properties to animate together, then while those properties
199     * are animating, to request some other properties to animate together. The way that
200     * works is by having this map associate the group of properties being animated with the
201     * animator handling the animation. On every update event for an Animator, we ask the
202     * map for the associated properties and set them accordingly.
203     */
204    private HashMap<Animator, PropertyBundle> mAnimatorMap =
205            new HashMap<Animator, PropertyBundle>();
206    private HashMap<Animator, Runnable> mAnimatorSetupMap;
207    private HashMap<Animator, Runnable> mAnimatorCleanupMap;
208    private HashMap<Animator, Runnable> mAnimatorOnStartMap;
209    private HashMap<Animator, Runnable> mAnimatorOnEndMap;
210
211    /**
212     * This is the information we need to set each property during the animation.
213     * mNameConstant is used to set the appropriate field in View, and the from/delta
214     * values are used to calculate the animated value for a given animation fraction
215     * during the animation.
216     */
217    private static class NameValuesHolder {
218        int mNameConstant;
219        float mFromValue;
220        float mDeltaValue;
221        NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
222            mNameConstant = nameConstant;
223            mFromValue = fromValue;
224            mDeltaValue = deltaValue;
225        }
226    }
227
228    /**
229     * Constructor, called by View. This is private by design, as the user should only
230     * get a ViewPropertyAnimator by calling View.animate().
231     *
232     * @param view The View associated with this ViewPropertyAnimator
233     */
234    ViewPropertyAnimator(View view) {
235        mView = view;
236        view.ensureTransformationInfo();
237    }
238
239    /**
240     * Sets the duration for the underlying animator that animates the requested properties.
241     * By default, the animator uses the default value for ValueAnimator. Calling this method
242     * will cause the declared value to be used instead.
243     * @param duration The length of ensuing property animations, in milliseconds. The value
244     * cannot be negative.
245     * @return This object, allowing calls to methods in this class to be chained.
246     */
247    public ViewPropertyAnimator setDuration(long duration) {
248        if (duration < 0) {
249            throw new IllegalArgumentException("Animators cannot have negative duration: " +
250                    duration);
251        }
252        mDurationSet = true;
253        mDuration = duration;
254        return this;
255    }
256
257    /**
258     * Returns the current duration of property animations. If the duration was set on this
259     * object, that value is returned. Otherwise, the default value of the underlying Animator
260     * is returned.
261     *
262     * @see #setDuration(long)
263     * @return The duration of animations, in milliseconds.
264     */
265    public long getDuration() {
266        if (mDurationSet) {
267            return mDuration;
268        } else {
269            // Just return the default from ValueAnimator, since that's what we'd get if
270            // the value has not been set otherwise
271            return new ValueAnimator().getDuration();
272        }
273    }
274
275    /**
276     * Returns the current startDelay of property animations. If the startDelay was set on this
277     * object, that value is returned. Otherwise, the default value of the underlying Animator
278     * is returned.
279     *
280     * @see #setStartDelay(long)
281     * @return The startDelay of animations, in milliseconds.
282     */
283    public long getStartDelay() {
284        if (mStartDelaySet) {
285            return mStartDelay;
286        } else {
287            // Just return the default from ValueAnimator (0), since that's what we'd get if
288            // the value has not been set otherwise
289            return 0;
290        }
291    }
292
293    /**
294     * Sets the startDelay for the underlying animator that animates the requested properties.
295     * By default, the animator uses the default value for ValueAnimator. Calling this method
296     * will cause the declared value to be used instead.
297     * @param startDelay The delay of ensuing property animations, in milliseconds. The value
298     * cannot be negative.
299     * @return This object, allowing calls to methods in this class to be chained.
300     */
301    public ViewPropertyAnimator setStartDelay(long startDelay) {
302        if (startDelay < 0) {
303            throw new IllegalArgumentException("Animators cannot have negative duration: " +
304                    startDelay);
305        }
306        mStartDelaySet = true;
307        mStartDelay = startDelay;
308        return this;
309    }
310
311    /**
312     * Sets the interpolator for the underlying animator that animates the requested properties.
313     * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
314     * will cause the declared object to be used instead.
315     *
316     * @param interpolator The TimeInterpolator to be used for ensuing property animations.
317     * @return This object, allowing calls to methods in this class to be chained.
318     */
319    public ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
320        mInterpolatorSet = true;
321        mInterpolator = interpolator;
322        return this;
323    }
324
325    /**
326     * Sets a listener for events in the underlying Animators that run the property
327     * animations.
328     *
329     * @param listener The listener to be called with AnimatorListener events.
330     * @return This object, allowing calls to methods in this class to be chained.
331     */
332    public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
333        mListener = listener;
334        return this;
335    }
336
337    /**
338     * Starts the currently pending property animations immediately. Calling <code>start()</code>
339     * is optional because all animations start automatically at the next opportunity. However,
340     * if the animations are needed to start immediately and synchronously (not at the time when
341     * the next event is processed by the hierarchy, which is when the animations would begin
342     * otherwise), then this method can be used.
343     */
344    public void start() {
345        startAnimation();
346    }
347
348    /**
349     * Cancels all property animations that are currently running or pending.
350     */
351    public void cancel() {
352        if (mAnimatorMap.size() > 0) {
353            HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
354                    (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
355            Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
356            for (Animator runningAnim : animatorSet) {
357                runningAnim.cancel();
358            }
359        }
360        mPendingAnimations.clear();
361        mView.removeCallbacks(mAnimationStarter);
362    }
363
364    /**
365     * This method will cause the View's <code>x</code> property to be animated to the
366     * specified value. Animations already running on the property will be canceled.
367     *
368     * @param value The value to be animated to.
369     * @see View#setX(float)
370     * @return This object, allowing calls to methods in this class to be chained.
371     */
372    public ViewPropertyAnimator x(float value) {
373        animateProperty(X, value);
374        return this;
375    }
376
377    /**
378     * This method will cause the View's <code>x</code> property to be animated by the
379     * specified value. Animations already running on the property will be canceled.
380     *
381     * @param value The amount to be animated by, as an offset from the current value.
382     * @see View#setX(float)
383     * @return This object, allowing calls to methods in this class to be chained.
384     */
385    public ViewPropertyAnimator xBy(float value) {
386        animatePropertyBy(X, value);
387        return this;
388    }
389
390    /**
391     * This method will cause the View's <code>y</code> property to be animated to the
392     * specified value. Animations already running on the property will be canceled.
393     *
394     * @param value The value to be animated to.
395     * @see View#setY(float)
396     * @return This object, allowing calls to methods in this class to be chained.
397     */
398    public ViewPropertyAnimator y(float value) {
399        animateProperty(Y, value);
400        return this;
401    }
402
403    /**
404     * This method will cause the View's <code>y</code> property to be animated by the
405     * specified value. Animations already running on the property will be canceled.
406     *
407     * @param value The amount to be animated by, as an offset from the current value.
408     * @see View#setY(float)
409     * @return This object, allowing calls to methods in this class to be chained.
410     */
411    public ViewPropertyAnimator yBy(float value) {
412        animatePropertyBy(Y, value);
413        return this;
414    }
415
416    /**
417     * This method will cause the View's <code>rotation</code> property to be animated to the
418     * specified value. Animations already running on the property will be canceled.
419     *
420     * @param value The value to be animated to.
421     * @see View#setRotation(float)
422     * @return This object, allowing calls to methods in this class to be chained.
423     */
424    public ViewPropertyAnimator rotation(float value) {
425        animateProperty(ROTATION, value);
426        return this;
427    }
428
429    /**
430     * This method will cause the View's <code>rotation</code> property to be animated by the
431     * specified value. Animations already running on the property will be canceled.
432     *
433     * @param value The amount to be animated by, as an offset from the current value.
434     * @see View#setRotation(float)
435     * @return This object, allowing calls to methods in this class to be chained.
436     */
437    public ViewPropertyAnimator rotationBy(float value) {
438        animatePropertyBy(ROTATION, value);
439        return this;
440    }
441
442    /**
443     * This method will cause the View's <code>rotationX</code> property to be animated to the
444     * specified value. Animations already running on the property will be canceled.
445     *
446     * @param value The value to be animated to.
447     * @see View#setRotationX(float)
448     * @return This object, allowing calls to methods in this class to be chained.
449     */
450    public ViewPropertyAnimator rotationX(float value) {
451        animateProperty(ROTATION_X, value);
452        return this;
453    }
454
455    /**
456     * This method will cause the View's <code>rotationX</code> property to be animated by the
457     * specified value. Animations already running on the property will be canceled.
458     *
459     * @param value The amount to be animated by, as an offset from the current value.
460     * @see View#setRotationX(float)
461     * @return This object, allowing calls to methods in this class to be chained.
462     */
463    public ViewPropertyAnimator rotationXBy(float value) {
464        animatePropertyBy(ROTATION_X, value);
465        return this;
466    }
467
468    /**
469     * This method will cause the View's <code>rotationY</code> property to be animated to the
470     * specified value. Animations already running on the property will be canceled.
471     *
472     * @param value The value to be animated to.
473     * @see View#setRotationY(float)
474     * @return This object, allowing calls to methods in this class to be chained.
475     */
476    public ViewPropertyAnimator rotationY(float value) {
477        animateProperty(ROTATION_Y, value);
478        return this;
479    }
480
481    /**
482     * This method will cause the View's <code>rotationY</code> property to be animated by the
483     * specified value. Animations already running on the property will be canceled.
484     *
485     * @param value The amount to be animated by, as an offset from the current value.
486     * @see View#setRotationY(float)
487     * @return This object, allowing calls to methods in this class to be chained.
488     */
489    public ViewPropertyAnimator rotationYBy(float value) {
490        animatePropertyBy(ROTATION_Y, value);
491        return this;
492    }
493
494    /**
495     * This method will cause the View's <code>translationX</code> property to be animated to the
496     * specified value. Animations already running on the property will be canceled.
497     *
498     * @param value The value to be animated to.
499     * @see View#setTranslationX(float)
500     * @return This object, allowing calls to methods in this class to be chained.
501     */
502    public ViewPropertyAnimator translationX(float value) {
503        animateProperty(TRANSLATION_X, value);
504        return this;
505    }
506
507    /**
508     * This method will cause the View's <code>translationX</code> property to be animated by the
509     * specified value. Animations already running on the property will be canceled.
510     *
511     * @param value The amount to be animated by, as an offset from the current value.
512     * @see View#setTranslationX(float)
513     * @return This object, allowing calls to methods in this class to be chained.
514     */
515    public ViewPropertyAnimator translationXBy(float value) {
516        animatePropertyBy(TRANSLATION_X, value);
517        return this;
518    }
519
520    /**
521     * This method will cause the View's <code>translationY</code> property to be animated to the
522     * specified value. Animations already running on the property will be canceled.
523     *
524     * @param value The value to be animated to.
525     * @see View#setTranslationY(float)
526     * @return This object, allowing calls to methods in this class to be chained.
527     */
528    public ViewPropertyAnimator translationY(float value) {
529        animateProperty(TRANSLATION_Y, value);
530        return this;
531    }
532
533    /**
534     * This method will cause the View's <code>translationY</code> property to be animated by the
535     * specified value. Animations already running on the property will be canceled.
536     *
537     * @param value The amount to be animated by, as an offset from the current value.
538     * @see View#setTranslationY(float)
539     * @return This object, allowing calls to methods in this class to be chained.
540     */
541    public ViewPropertyAnimator translationYBy(float value) {
542        animatePropertyBy(TRANSLATION_Y, value);
543        return this;
544    }
545
546    /**
547     * This method will cause the View's <code>scaleX</code> property to be animated to the
548     * specified value. Animations already running on the property will be canceled.
549     *
550     * @param value The value to be animated to.
551     * @see View#setScaleX(float)
552     * @return This object, allowing calls to methods in this class to be chained.
553     */
554    public ViewPropertyAnimator scaleX(float value) {
555        animateProperty(SCALE_X, value);
556        return this;
557    }
558
559    /**
560     * This method will cause the View's <code>scaleX</code> property to be animated by the
561     * specified value. Animations already running on the property will be canceled.
562     *
563     * @param value The amount to be animated by, as an offset from the current value.
564     * @see View#setScaleX(float)
565     * @return This object, allowing calls to methods in this class to be chained.
566     */
567    public ViewPropertyAnimator scaleXBy(float value) {
568        animatePropertyBy(SCALE_X, value);
569        return this;
570    }
571
572    /**
573     * This method will cause the View's <code>scaleY</code> property to be animated to the
574     * specified value. Animations already running on the property will be canceled.
575     *
576     * @param value The value to be animated to.
577     * @see View#setScaleY(float)
578     * @return This object, allowing calls to methods in this class to be chained.
579     */
580    public ViewPropertyAnimator scaleY(float value) {
581        animateProperty(SCALE_Y, value);
582        return this;
583    }
584
585    /**
586     * This method will cause the View's <code>scaleY</code> property to be animated by the
587     * specified value. Animations already running on the property will be canceled.
588     *
589     * @param value The amount to be animated by, as an offset from the current value.
590     * @see View#setScaleY(float)
591     * @return This object, allowing calls to methods in this class to be chained.
592     */
593    public ViewPropertyAnimator scaleYBy(float value) {
594        animatePropertyBy(SCALE_Y, value);
595        return this;
596    }
597
598    /**
599     * This method will cause the View's <code>alpha</code> property to be animated to the
600     * specified value. Animations already running on the property will be canceled.
601     *
602     * @param value The value to be animated to.
603     * @see View#setAlpha(float)
604     * @return This object, allowing calls to methods in this class to be chained.
605     */
606    public ViewPropertyAnimator alpha(float value) {
607        animateProperty(ALPHA, value);
608        return this;
609    }
610
611    /**
612     * This method will cause the View's <code>alpha</code> property to be animated by the
613     * specified value. Animations already running on the property will be canceled.
614     *
615     * @param value The amount to be animated by, as an offset from the current value.
616     * @see View#setAlpha(float)
617     * @return This object, allowing calls to methods in this class to be chained.
618     */
619    public ViewPropertyAnimator alphaBy(float value) {
620        animatePropertyBy(ALPHA, value);
621        return this;
622    }
623
624    /**
625     * The View associated with this ViewPropertyAnimator will have its
626     * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
627     * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation. This state
628     * is not persistent, either on the View or on this ViewPropertyAnimator: the layer type
629     * of the View will be restored when the animation ends to what it was when this method was
630     * called, and this setting on ViewPropertyAnimator is only valid for the next animation.
631     * Note that calling this method and then independently setting the layer type of the View
632     * (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will result
633     * in some inconsistency, including having the layer type restored to its pre-withLayer()
634     * value when the animation ends.
635     *
636     * @see View#setLayerType(int, android.graphics.Paint)
637     * @return This object, allowing calls to methods in this class to be chained.
638     */
639    public ViewPropertyAnimator withLayer() {
640         mPendingSetupAction= new Runnable() {
641            @Override
642            public void run() {
643                mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
644            }
645        };
646        final int currentLayerType = mView.getLayerType();
647        mPendingCleanupAction = new Runnable() {
648            @Override
649            public void run() {
650                mView.setLayerType(currentLayerType, null);
651            }
652        };
653        if (mAnimatorSetupMap == null) {
654            mAnimatorSetupMap = new HashMap<Animator, Runnable>();
655        }
656        if (mAnimatorCleanupMap == null) {
657            mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
658        }
659
660        return this;
661    }
662
663    /**
664     * Specifies an action to take place when the next animation runs. If there is a
665     * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
666     * action will run after that startDelay expires, when the actual animation begins.
667     * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
668     * choreographing ViewPropertyAnimator animations with other animations or actions
669     * in the application.
670     *
671     * @param runnable The action to run when the next animation starts.
672     * @return This object, allowing calls to methods in this class to be chained.
673     */
674    public ViewPropertyAnimator withStartAction(Runnable runnable) {
675        mPendingOnStartAction = runnable;
676        if (runnable != null && mAnimatorOnStartMap == null) {
677            mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
678        }
679        return this;
680    }
681
682    /**
683     * Specifies an action to take place when the next animation ends. The action is only
684     * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
685     * that animation, the runnable will not run.
686     * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
687     * choreographing ViewPropertyAnimator animations with other animations or actions
688     * in the application.
689     *
690     * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
691     * <pre>
692     *     Runnable endAction = new Runnable() {
693     *         public void run() {
694     *             view.animate().x(0);
695     *         }
696     *     };
697     *     view.animate().x(200).onEnd(endAction);
698     * </pre>
699     *
700     * @param runnable The action to run when the next animation ends.
701     * @return This object, allowing calls to methods in this class to be chained.
702     */
703    public ViewPropertyAnimator withEndAction(Runnable runnable) {
704        mPendingOnEndAction = runnable;
705        if (runnable != null && mAnimatorOnEndMap == null) {
706            mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
707        }
708        return this;
709    }
710
711    /**
712     * Starts the underlying Animator for a set of properties. We use a single animator that
713     * simply runs from 0 to 1, and then use that fractional value to set each property
714     * value accordingly.
715     */
716    private void startAnimation() {
717        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
718        ArrayList<NameValuesHolder> nameValueList =
719                (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
720        mPendingAnimations.clear();
721        int propertyMask = 0;
722        int propertyCount = nameValueList.size();
723        for (int i = 0; i < propertyCount; ++i) {
724            NameValuesHolder nameValuesHolder = nameValueList.get(i);
725            propertyMask |= nameValuesHolder.mNameConstant;
726        }
727        mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
728        if (mPendingSetupAction != null) {
729            mAnimatorSetupMap.put(animator, mPendingSetupAction);
730            mPendingSetupAction = null;
731        }
732        if (mPendingCleanupAction != null) {
733            mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
734            mPendingCleanupAction = null;
735        }
736        if (mPendingOnStartAction != null) {
737            mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
738            mPendingOnStartAction = null;
739        }
740        if (mPendingOnEndAction != null) {
741            mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
742            mPendingOnEndAction = null;
743        }
744        animator.addUpdateListener(mAnimatorEventListener);
745        animator.addListener(mAnimatorEventListener);
746        if (mStartDelaySet) {
747            animator.setStartDelay(mStartDelay);
748        }
749        if (mDurationSet) {
750            animator.setDuration(mDuration);
751        }
752        if (mInterpolatorSet) {
753            animator.setInterpolator(mInterpolator);
754        }
755        animator.start();
756    }
757
758    /**
759     * Utility function, called by the various x(), y(), etc. methods. This stores the
760     * constant name for the property along with the from/delta values that will be used to
761     * calculate and set the property during the animation. This structure is added to the
762     * pending animations, awaiting the eventual start() of the underlying animator. A
763     * Runnable is posted to start the animation, and any pending such Runnable is canceled
764     * (which enables us to end up starting just one animator for all of the properties
765     * specified at one time).
766     *
767     * @param constantName The specifier for the property being animated
768     * @param toValue The value to which the property will animate
769     */
770    private void animateProperty(int constantName, float toValue) {
771        float fromValue = getValue(constantName);
772        float deltaValue = toValue - fromValue;
773        animatePropertyBy(constantName, fromValue, deltaValue);
774    }
775
776    /**
777     * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
778     * just like animateProperty(), except the value is an offset from the property's
779     * current value, instead of an absolute "to" value.
780     *
781     * @param constantName The specifier for the property being animated
782     * @param byValue The amount by which the property will change
783     */
784    private void animatePropertyBy(int constantName, float byValue) {
785        float fromValue = getValue(constantName);
786        animatePropertyBy(constantName, fromValue, byValue);
787    }
788
789    /**
790     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
791     * details of adding a pending animation and posting the request to start the animation.
792     *
793     * @param constantName The specifier for the property being animated
794     * @param startValue The starting value of the property
795     * @param byValue The amount by which the property will change
796     */
797    private void animatePropertyBy(int constantName, float startValue, float byValue) {
798        // First, cancel any existing animations on this property
799        if (mAnimatorMap.size() > 0) {
800            Animator animatorToCancel = null;
801            Set<Animator> animatorSet = mAnimatorMap.keySet();
802            for (Animator runningAnim : animatorSet) {
803                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
804                if (bundle.cancel(constantName)) {
805                    // property was canceled - cancel the animation if it's now empty
806                    // Note that it's safe to break out here because every new animation
807                    // on a property will cancel a previous animation on that property, so
808                    // there can only ever be one such animation running.
809                    if (bundle.mPropertyMask == NONE) {
810                        // the animation is no longer changing anything - cancel it
811                        animatorToCancel = runningAnim;
812                        break;
813                    }
814                }
815            }
816            if (animatorToCancel != null) {
817                animatorToCancel.cancel();
818            }
819        }
820
821        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
822        mPendingAnimations.add(nameValuePair);
823        mView.removeCallbacks(mAnimationStarter);
824        mView.post(mAnimationStarter);
825    }
826
827    /**
828     * This method handles setting the property values directly in the View object's fields.
829     * propertyConstant tells it which property should be set, value is the value to set
830     * the property to.
831     *
832     * @param propertyConstant The property to be set
833     * @param value The value to set the property to
834     */
835    private void setValue(int propertyConstant, float value) {
836        final View.TransformationInfo info = mView.mTransformationInfo;
837        switch (propertyConstant) {
838            case TRANSLATION_X:
839                info.mTranslationX = value;
840                break;
841            case TRANSLATION_Y:
842                info.mTranslationY = value;
843                break;
844            case ROTATION:
845                info.mRotation = value;
846                break;
847            case ROTATION_X:
848                info.mRotationX = value;
849                break;
850            case ROTATION_Y:
851                info.mRotationY = value;
852                break;
853            case SCALE_X:
854                info.mScaleX = value;
855                break;
856            case SCALE_Y:
857                info.mScaleY = value;
858                break;
859            case X:
860                info.mTranslationX = value - mView.mLeft;
861                break;
862            case Y:
863                info.mTranslationY = value - mView.mTop;
864                break;
865            case ALPHA:
866                info.mAlpha = value;
867                break;
868        }
869        // TODO: optimize to set only the properties that have changed
870        mView.setDisplayListProperties();
871    }
872
873    /**
874     * This method gets the value of the named property from the View object.
875     *
876     * @param propertyConstant The property whose value should be returned
877     * @return float The value of the named property
878     */
879    private float getValue(int propertyConstant) {
880        final View.TransformationInfo info = mView.mTransformationInfo;
881        switch (propertyConstant) {
882            case TRANSLATION_X:
883                return info.mTranslationX;
884            case TRANSLATION_Y:
885                return info.mTranslationY;
886            case ROTATION:
887                return info.mRotation;
888            case ROTATION_X:
889                return info.mRotationX;
890            case ROTATION_Y:
891                return info.mRotationY;
892            case SCALE_X:
893                return info.mScaleX;
894            case SCALE_Y:
895                return info.mScaleY;
896            case X:
897                return mView.mLeft + info.mTranslationX;
898            case Y:
899                return mView.mTop + info.mTranslationY;
900            case ALPHA:
901                return info.mAlpha;
902        }
903        return 0;
904    }
905
906    /**
907     * Utility class that handles the various Animator events. The only ones we care
908     * about are the end event (which we use to clean up the animator map when an animator
909     * finishes) and the update event (which we use to calculate the current value of each
910     * property and then set it on the view object).
911     */
912    private class AnimatorEventListener
913            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
914        @Override
915        public void onAnimationStart(Animator animation) {
916            if (mAnimatorSetupMap != null) {
917                Runnable r = mAnimatorSetupMap.get(animation);
918                if (r != null) {
919                    r.run();
920                }
921                mAnimatorSetupMap.remove(animation);
922            }
923            if (mAnimatorOnStartMap != null) {
924                Runnable r = mAnimatorOnStartMap.get(animation);
925                if (r != null) {
926                    r.run();
927                }
928                mAnimatorOnStartMap.remove(animation);
929            }
930            if (mListener != null) {
931                mListener.onAnimationStart(animation);
932            }
933        }
934
935        @Override
936        public void onAnimationCancel(Animator animation) {
937            if (mListener != null) {
938                mListener.onAnimationCancel(animation);
939            }
940            if (mAnimatorOnEndMap != null) {
941                mAnimatorOnEndMap.remove(animation);
942            }
943        }
944
945        @Override
946        public void onAnimationRepeat(Animator animation) {
947            if (mListener != null) {
948                mListener.onAnimationRepeat(animation);
949            }
950        }
951
952        @Override
953        public void onAnimationEnd(Animator animation) {
954            if (mListener != null) {
955                mListener.onAnimationEnd(animation);
956            }
957            if (mAnimatorOnEndMap != null) {
958                Runnable r = mAnimatorOnEndMap.get(animation);
959                if (r != null) {
960                    r.run();
961                }
962                mAnimatorOnEndMap.remove(animation);
963            }
964            if (mAnimatorCleanupMap != null) {
965                Runnable r = mAnimatorCleanupMap.get(animation);
966                if (r != null) {
967                    r.run();
968                }
969                mAnimatorCleanupMap.remove(animation);
970            }
971            mAnimatorMap.remove(animation);
972        }
973
974        /**
975         * Calculate the current value for each property and set it on the view. Invalidate
976         * the view object appropriately, depending on which properties are being animated.
977         *
978         * @param animation The animator associated with the properties that need to be
979         * set. This animator holds the animation fraction which we will use to calculate
980         * the current value of each property.
981         */
982        @Override
983        public void onAnimationUpdate(ValueAnimator animation) {
984            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
985            if (propertyBundle == null) {
986                // Shouldn't happen, but just to play it safe
987                return;
988            }
989            boolean useDisplayListProperties = View.USE_DISPLAY_LIST_PROPERTIES &&
990                    mView.mDisplayList != null;
991
992            // alpha requires slightly different treatment than the other (transform) properties.
993            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
994            // logic is dependent on how the view handles an internal call to onSetAlpha().
995            // We track what kinds of properties are set, and how alpha is handled when it is
996            // set, and perform the invalidation steps appropriately.
997            boolean alphaHandled = false;
998            if (!useDisplayListProperties) {
999                mView.invalidateParentCaches();
1000            }
1001            float fraction = animation.getAnimatedFraction();
1002            int propertyMask = propertyBundle.mPropertyMask;
1003            if ((propertyMask & TRANSFORM_MASK) != 0) {
1004                mView.invalidateViewProperty(false, false);
1005            }
1006            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
1007            if (valueList != null) {
1008                int count = valueList.size();
1009                for (int i = 0; i < count; ++i) {
1010                    NameValuesHolder values = valueList.get(i);
1011                    float value = values.mFromValue + fraction * values.mDeltaValue;
1012                    if (values.mNameConstant == ALPHA) {
1013                        alphaHandled = mView.setAlphaNoInvalidation(value);
1014                    } else {
1015                        setValue(values.mNameConstant, value);
1016                    }
1017                }
1018            }
1019            if ((propertyMask & TRANSFORM_MASK) != 0) {
1020                mView.mTransformationInfo.mMatrixDirty = true;
1021                if (!useDisplayListProperties) {
1022                    mView.mPrivateFlags |= View.DRAWN; // force another invalidation
1023                }
1024            }
1025            // invalidate(false) in all cases except if alphaHandled gets set to true
1026            // via the call to setAlphaNoInvalidation(), above
1027            if (alphaHandled) {
1028                mView.invalidate(true);
1029            } else {
1030                mView.invalidateViewProperty(false, false);
1031            }
1032        }
1033    }
1034}
1035