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     * Returns the timing interpolator that this animation uses.
327     *
328     * @return The timing interpolator for this animation.
329     */
330    public TimeInterpolator getInterpolator() {
331        return null;
332    }
333
334    /**
335     * Sets a listener for events in the underlying Animators that run the property
336     * animations.
337     *
338     * @param listener The listener to be called with AnimatorListener events.
339     * @return This object, allowing calls to methods in this class to be chained.
340     */
341    public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
342        mListener = listener;
343        return this;
344    }
345
346    /**
347     * Starts the currently pending property animations immediately. Calling <code>start()</code>
348     * is optional because all animations start automatically at the next opportunity. However,
349     * if the animations are needed to start immediately and synchronously (not at the time when
350     * the next event is processed by the hierarchy, which is when the animations would begin
351     * otherwise), then this method can be used.
352     */
353    public void start() {
354        mView.removeCallbacks(mAnimationStarter);
355        startAnimation();
356    }
357
358    /**
359     * Cancels all property animations that are currently running or pending.
360     */
361    public void cancel() {
362        if (mAnimatorMap.size() > 0) {
363            HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
364                    (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
365            Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
366            for (Animator runningAnim : animatorSet) {
367                runningAnim.cancel();
368            }
369        }
370        mPendingAnimations.clear();
371        mView.removeCallbacks(mAnimationStarter);
372    }
373
374    /**
375     * This method will cause the View's <code>x</code> property to be animated to the
376     * specified value. Animations already running on the property will be canceled.
377     *
378     * @param value The value to be animated to.
379     * @see View#setX(float)
380     * @return This object, allowing calls to methods in this class to be chained.
381     */
382    public ViewPropertyAnimator x(float value) {
383        animateProperty(X, value);
384        return this;
385    }
386
387    /**
388     * This method will cause the View's <code>x</code> property to be animated by the
389     * specified value. Animations already running on the property will be canceled.
390     *
391     * @param value The amount to be animated by, as an offset from the current value.
392     * @see View#setX(float)
393     * @return This object, allowing calls to methods in this class to be chained.
394     */
395    public ViewPropertyAnimator xBy(float value) {
396        animatePropertyBy(X, value);
397        return this;
398    }
399
400    /**
401     * This method will cause the View's <code>y</code> property to be animated to the
402     * specified value. Animations already running on the property will be canceled.
403     *
404     * @param value The value to be animated to.
405     * @see View#setY(float)
406     * @return This object, allowing calls to methods in this class to be chained.
407     */
408    public ViewPropertyAnimator y(float value) {
409        animateProperty(Y, value);
410        return this;
411    }
412
413    /**
414     * This method will cause the View's <code>y</code> property to be animated by the
415     * specified value. Animations already running on the property will be canceled.
416     *
417     * @param value The amount to be animated by, as an offset from the current value.
418     * @see View#setY(float)
419     * @return This object, allowing calls to methods in this class to be chained.
420     */
421    public ViewPropertyAnimator yBy(float value) {
422        animatePropertyBy(Y, value);
423        return this;
424    }
425
426    /**
427     * This method will cause the View's <code>rotation</code> property to be animated to the
428     * specified value. Animations already running on the property will be canceled.
429     *
430     * @param value The value to be animated to.
431     * @see View#setRotation(float)
432     * @return This object, allowing calls to methods in this class to be chained.
433     */
434    public ViewPropertyAnimator rotation(float value) {
435        animateProperty(ROTATION, value);
436        return this;
437    }
438
439    /**
440     * This method will cause the View's <code>rotation</code> property to be animated by the
441     * specified value. Animations already running on the property will be canceled.
442     *
443     * @param value The amount to be animated by, as an offset from the current value.
444     * @see View#setRotation(float)
445     * @return This object, allowing calls to methods in this class to be chained.
446     */
447    public ViewPropertyAnimator rotationBy(float value) {
448        animatePropertyBy(ROTATION, value);
449        return this;
450    }
451
452    /**
453     * This method will cause the View's <code>rotationX</code> property to be animated to the
454     * specified value. Animations already running on the property will be canceled.
455     *
456     * @param value The value to be animated to.
457     * @see View#setRotationX(float)
458     * @return This object, allowing calls to methods in this class to be chained.
459     */
460    public ViewPropertyAnimator rotationX(float value) {
461        animateProperty(ROTATION_X, value);
462        return this;
463    }
464
465    /**
466     * This method will cause the View's <code>rotationX</code> property to be animated by the
467     * specified value. Animations already running on the property will be canceled.
468     *
469     * @param value The amount to be animated by, as an offset from the current value.
470     * @see View#setRotationX(float)
471     * @return This object, allowing calls to methods in this class to be chained.
472     */
473    public ViewPropertyAnimator rotationXBy(float value) {
474        animatePropertyBy(ROTATION_X, value);
475        return this;
476    }
477
478    /**
479     * This method will cause the View's <code>rotationY</code> property to be animated to the
480     * specified value. Animations already running on the property will be canceled.
481     *
482     * @param value The value to be animated to.
483     * @see View#setRotationY(float)
484     * @return This object, allowing calls to methods in this class to be chained.
485     */
486    public ViewPropertyAnimator rotationY(float value) {
487        animateProperty(ROTATION_Y, value);
488        return this;
489    }
490
491    /**
492     * This method will cause the View's <code>rotationY</code> property to be animated by the
493     * specified value. Animations already running on the property will be canceled.
494     *
495     * @param value The amount to be animated by, as an offset from the current value.
496     * @see View#setRotationY(float)
497     * @return This object, allowing calls to methods in this class to be chained.
498     */
499    public ViewPropertyAnimator rotationYBy(float value) {
500        animatePropertyBy(ROTATION_Y, value);
501        return this;
502    }
503
504    /**
505     * This method will cause the View's <code>translationX</code> property to be animated to the
506     * specified value. Animations already running on the property will be canceled.
507     *
508     * @param value The value to be animated to.
509     * @see View#setTranslationX(float)
510     * @return This object, allowing calls to methods in this class to be chained.
511     */
512    public ViewPropertyAnimator translationX(float value) {
513        animateProperty(TRANSLATION_X, value);
514        return this;
515    }
516
517    /**
518     * This method will cause the View's <code>translationX</code> property to be animated by the
519     * specified value. Animations already running on the property will be canceled.
520     *
521     * @param value The amount to be animated by, as an offset from the current value.
522     * @see View#setTranslationX(float)
523     * @return This object, allowing calls to methods in this class to be chained.
524     */
525    public ViewPropertyAnimator translationXBy(float value) {
526        animatePropertyBy(TRANSLATION_X, value);
527        return this;
528    }
529
530    /**
531     * This method will cause the View's <code>translationY</code> property to be animated to the
532     * specified value. Animations already running on the property will be canceled.
533     *
534     * @param value The value to be animated to.
535     * @see View#setTranslationY(float)
536     * @return This object, allowing calls to methods in this class to be chained.
537     */
538    public ViewPropertyAnimator translationY(float value) {
539        animateProperty(TRANSLATION_Y, value);
540        return this;
541    }
542
543    /**
544     * This method will cause the View's <code>translationY</code> property to be animated by the
545     * specified value. Animations already running on the property will be canceled.
546     *
547     * @param value The amount to be animated by, as an offset from the current value.
548     * @see View#setTranslationY(float)
549     * @return This object, allowing calls to methods in this class to be chained.
550     */
551    public ViewPropertyAnimator translationYBy(float value) {
552        animatePropertyBy(TRANSLATION_Y, value);
553        return this;
554    }
555
556    /**
557     * This method will cause the View's <code>scaleX</code> property to be animated to the
558     * specified value. Animations already running on the property will be canceled.
559     *
560     * @param value The value to be animated to.
561     * @see View#setScaleX(float)
562     * @return This object, allowing calls to methods in this class to be chained.
563     */
564    public ViewPropertyAnimator scaleX(float value) {
565        animateProperty(SCALE_X, value);
566        return this;
567    }
568
569    /**
570     * This method will cause the View's <code>scaleX</code> property to be animated by the
571     * specified value. Animations already running on the property will be canceled.
572     *
573     * @param value The amount to be animated by, as an offset from the current value.
574     * @see View#setScaleX(float)
575     * @return This object, allowing calls to methods in this class to be chained.
576     */
577    public ViewPropertyAnimator scaleXBy(float value) {
578        animatePropertyBy(SCALE_X, value);
579        return this;
580    }
581
582    /**
583     * This method will cause the View's <code>scaleY</code> property to be animated to the
584     * specified value. Animations already running on the property will be canceled.
585     *
586     * @param value The value to be animated to.
587     * @see View#setScaleY(float)
588     * @return This object, allowing calls to methods in this class to be chained.
589     */
590    public ViewPropertyAnimator scaleY(float value) {
591        animateProperty(SCALE_Y, value);
592        return this;
593    }
594
595    /**
596     * This method will cause the View's <code>scaleY</code> property to be animated by the
597     * specified value. Animations already running on the property will be canceled.
598     *
599     * @param value The amount to be animated by, as an offset from the current value.
600     * @see View#setScaleY(float)
601     * @return This object, allowing calls to methods in this class to be chained.
602     */
603    public ViewPropertyAnimator scaleYBy(float value) {
604        animatePropertyBy(SCALE_Y, value);
605        return this;
606    }
607
608    /**
609     * This method will cause the View's <code>alpha</code> property to be animated to the
610     * specified value. Animations already running on the property will be canceled.
611     *
612     * @param value The value to be animated to.
613     * @see View#setAlpha(float)
614     * @return This object, allowing calls to methods in this class to be chained.
615     */
616    public ViewPropertyAnimator alpha(float value) {
617        animateProperty(ALPHA, value);
618        return this;
619    }
620
621    /**
622     * This method will cause the View's <code>alpha</code> property to be animated by the
623     * specified value. Animations already running on the property will be canceled.
624     *
625     * @param value The amount to be animated by, as an offset from the current value.
626     * @see View#setAlpha(float)
627     * @return This object, allowing calls to methods in this class to be chained.
628     */
629    public ViewPropertyAnimator alphaBy(float value) {
630        animatePropertyBy(ALPHA, value);
631        return this;
632    }
633
634    /**
635     * The View associated with this ViewPropertyAnimator will have its
636     * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
637     * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation.
638     * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE},
639     * the actual type of layer used internally depends on the runtime situation of the
640     * view. If the activity and this view are hardware-accelerated, then the layer will be
641     * accelerated as well. If the activity or the view is not accelerated, then the layer will
642     * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}.
643     *
644     * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
645     * layer type of the View will be restored when the animation ends to what it was when this
646     * method was called, and this setting on ViewPropertyAnimator is only valid for the next
647     * animation. Note that calling this method and then independently setting the layer type of
648     * the View (by a direct call to {@link View#setLayerType(int, android.graphics.Paint)}) will
649     * result in some inconsistency, including having the layer type restored to its pre-withLayer()
650     * value when the animation ends.</p>
651     *
652     * @see View#setLayerType(int, android.graphics.Paint)
653     * @return This object, allowing calls to methods in this class to be chained.
654     */
655    public ViewPropertyAnimator withLayer() {
656         mPendingSetupAction= new Runnable() {
657            @Override
658            public void run() {
659                mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
660            }
661        };
662        final int currentLayerType = mView.getLayerType();
663        mPendingCleanupAction = new Runnable() {
664            @Override
665            public void run() {
666                mView.setLayerType(currentLayerType, null);
667            }
668        };
669        if (mAnimatorSetupMap == null) {
670            mAnimatorSetupMap = new HashMap<Animator, Runnable>();
671        }
672        if (mAnimatorCleanupMap == null) {
673            mAnimatorCleanupMap = new HashMap<Animator, Runnable>();
674        }
675
676        return this;
677    }
678
679    /**
680     * Specifies an action to take place when the next animation runs. If there is a
681     * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
682     * action will run after that startDelay expires, when the actual animation begins.
683     * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
684     * choreographing ViewPropertyAnimator animations with other animations or actions
685     * in the application.
686     *
687     * @param runnable The action to run when the next animation starts.
688     * @return This object, allowing calls to methods in this class to be chained.
689     */
690    public ViewPropertyAnimator withStartAction(Runnable runnable) {
691        mPendingOnStartAction = runnable;
692        if (runnable != null && mAnimatorOnStartMap == null) {
693            mAnimatorOnStartMap = new HashMap<Animator, Runnable>();
694        }
695        return this;
696    }
697
698    /**
699     * Specifies an action to take place when the next animation ends. The action is only
700     * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
701     * that animation, the runnable will not run.
702     * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
703     * choreographing ViewPropertyAnimator animations with other animations or actions
704     * in the application.
705     *
706     * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
707     * <pre>
708     *     Runnable endAction = new Runnable() {
709     *         public void run() {
710     *             view.animate().x(0);
711     *         }
712     *     };
713     *     view.animate().x(200).withEndAction(endAction);
714     * </pre>
715     *
716     * @param runnable The action to run when the next animation ends.
717     * @return This object, allowing calls to methods in this class to be chained.
718     */
719    public ViewPropertyAnimator withEndAction(Runnable runnable) {
720        mPendingOnEndAction = runnable;
721        if (runnable != null && mAnimatorOnEndMap == null) {
722            mAnimatorOnEndMap = new HashMap<Animator, Runnable>();
723        }
724        return this;
725    }
726
727    /**
728     * Starts the underlying Animator for a set of properties. We use a single animator that
729     * simply runs from 0 to 1, and then use that fractional value to set each property
730     * value accordingly.
731     */
732    private void startAnimation() {
733        mView.setHasTransientState(true);
734        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
735        ArrayList<NameValuesHolder> nameValueList =
736                (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
737        mPendingAnimations.clear();
738        int propertyMask = 0;
739        int propertyCount = nameValueList.size();
740        for (int i = 0; i < propertyCount; ++i) {
741            NameValuesHolder nameValuesHolder = nameValueList.get(i);
742            propertyMask |= nameValuesHolder.mNameConstant;
743        }
744        mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
745        if (mPendingSetupAction != null) {
746            mAnimatorSetupMap.put(animator, mPendingSetupAction);
747            mPendingSetupAction = null;
748        }
749        if (mPendingCleanupAction != null) {
750            mAnimatorCleanupMap.put(animator, mPendingCleanupAction);
751            mPendingCleanupAction = null;
752        }
753        if (mPendingOnStartAction != null) {
754            mAnimatorOnStartMap.put(animator, mPendingOnStartAction);
755            mPendingOnStartAction = null;
756        }
757        if (mPendingOnEndAction != null) {
758            mAnimatorOnEndMap.put(animator, mPendingOnEndAction);
759            mPendingOnEndAction = null;
760        }
761        animator.addUpdateListener(mAnimatorEventListener);
762        animator.addListener(mAnimatorEventListener);
763        if (mStartDelaySet) {
764            animator.setStartDelay(mStartDelay);
765        }
766        if (mDurationSet) {
767            animator.setDuration(mDuration);
768        }
769        if (mInterpolatorSet) {
770            animator.setInterpolator(mInterpolator);
771        }
772        animator.start();
773    }
774
775    /**
776     * Utility function, called by the various x(), y(), etc. methods. This stores the
777     * constant name for the property along with the from/delta values that will be used to
778     * calculate and set the property during the animation. This structure is added to the
779     * pending animations, awaiting the eventual start() of the underlying animator. A
780     * Runnable is posted to start the animation, and any pending such Runnable is canceled
781     * (which enables us to end up starting just one animator for all of the properties
782     * specified at one time).
783     *
784     * @param constantName The specifier for the property being animated
785     * @param toValue The value to which the property will animate
786     */
787    private void animateProperty(int constantName, float toValue) {
788        float fromValue = getValue(constantName);
789        float deltaValue = toValue - fromValue;
790        animatePropertyBy(constantName, fromValue, deltaValue);
791    }
792
793    /**
794     * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
795     * just like animateProperty(), except the value is an offset from the property's
796     * current value, instead of an absolute "to" value.
797     *
798     * @param constantName The specifier for the property being animated
799     * @param byValue The amount by which the property will change
800     */
801    private void animatePropertyBy(int constantName, float byValue) {
802        float fromValue = getValue(constantName);
803        animatePropertyBy(constantName, fromValue, byValue);
804    }
805
806    /**
807     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
808     * details of adding a pending animation and posting the request to start the animation.
809     *
810     * @param constantName The specifier for the property being animated
811     * @param startValue The starting value of the property
812     * @param byValue The amount by which the property will change
813     */
814    private void animatePropertyBy(int constantName, float startValue, float byValue) {
815        // First, cancel any existing animations on this property
816        if (mAnimatorMap.size() > 0) {
817            Animator animatorToCancel = null;
818            Set<Animator> animatorSet = mAnimatorMap.keySet();
819            for (Animator runningAnim : animatorSet) {
820                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
821                if (bundle.cancel(constantName)) {
822                    // property was canceled - cancel the animation if it's now empty
823                    // Note that it's safe to break out here because every new animation
824                    // on a property will cancel a previous animation on that property, so
825                    // there can only ever be one such animation running.
826                    if (bundle.mPropertyMask == NONE) {
827                        // the animation is no longer changing anything - cancel it
828                        animatorToCancel = runningAnim;
829                        break;
830                    }
831                }
832            }
833            if (animatorToCancel != null) {
834                animatorToCancel.cancel();
835            }
836        }
837
838        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
839        mPendingAnimations.add(nameValuePair);
840        mView.removeCallbacks(mAnimationStarter);
841        mView.postOnAnimation(mAnimationStarter);
842    }
843
844    /**
845     * This method handles setting the property values directly in the View object's fields.
846     * propertyConstant tells it which property should be set, value is the value to set
847     * the property to.
848     *
849     * @param propertyConstant The property to be set
850     * @param value The value to set the property to
851     */
852    private void setValue(int propertyConstant, float value) {
853        final View.TransformationInfo info = mView.mTransformationInfo;
854        final DisplayList displayList = mView.mDisplayList;
855        switch (propertyConstant) {
856            case TRANSLATION_X:
857                info.mTranslationX = value;
858                if (displayList != null) displayList.setTranslationX(value);
859                break;
860            case TRANSLATION_Y:
861                info.mTranslationY = value;
862                if (displayList != null) displayList.setTranslationY(value);
863                break;
864            case ROTATION:
865                info.mRotation = value;
866                if (displayList != null) displayList.setRotation(value);
867                break;
868            case ROTATION_X:
869                info.mRotationX = value;
870                if (displayList != null) displayList.setRotationX(value);
871                break;
872            case ROTATION_Y:
873                info.mRotationY = value;
874                if (displayList != null) displayList.setRotationY(value);
875                break;
876            case SCALE_X:
877                info.mScaleX = value;
878                if (displayList != null) displayList.setScaleX(value);
879                break;
880            case SCALE_Y:
881                info.mScaleY = value;
882                if (displayList != null) displayList.setScaleY(value);
883                break;
884            case X:
885                info.mTranslationX = value - mView.mLeft;
886                if (displayList != null) displayList.setTranslationX(value - mView.mLeft);
887                break;
888            case Y:
889                info.mTranslationY = value - mView.mTop;
890                if (displayList != null) displayList.setTranslationY(value - mView.mTop);
891                break;
892            case ALPHA:
893                info.mAlpha = value;
894                if (displayList != null) displayList.setAlpha(value);
895                break;
896        }
897    }
898
899    /**
900     * This method gets the value of the named property from the View object.
901     *
902     * @param propertyConstant The property whose value should be returned
903     * @return float The value of the named property
904     */
905    private float getValue(int propertyConstant) {
906        final View.TransformationInfo info = mView.mTransformationInfo;
907        switch (propertyConstant) {
908            case TRANSLATION_X:
909                return info.mTranslationX;
910            case TRANSLATION_Y:
911                return info.mTranslationY;
912            case ROTATION:
913                return info.mRotation;
914            case ROTATION_X:
915                return info.mRotationX;
916            case ROTATION_Y:
917                return info.mRotationY;
918            case SCALE_X:
919                return info.mScaleX;
920            case SCALE_Y:
921                return info.mScaleY;
922            case X:
923                return mView.mLeft + info.mTranslationX;
924            case Y:
925                return mView.mTop + info.mTranslationY;
926            case ALPHA:
927                return info.mAlpha;
928        }
929        return 0;
930    }
931
932    /**
933     * Utility class that handles the various Animator events. The only ones we care
934     * about are the end event (which we use to clean up the animator map when an animator
935     * finishes) and the update event (which we use to calculate the current value of each
936     * property and then set it on the view object).
937     */
938    private class AnimatorEventListener
939            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
940        @Override
941        public void onAnimationStart(Animator animation) {
942            if (mAnimatorSetupMap != null) {
943                Runnable r = mAnimatorSetupMap.get(animation);
944                if (r != null) {
945                    r.run();
946                }
947                mAnimatorSetupMap.remove(animation);
948            }
949            if (mAnimatorOnStartMap != null) {
950                Runnable r = mAnimatorOnStartMap.get(animation);
951                if (r != null) {
952                    r.run();
953                }
954                mAnimatorOnStartMap.remove(animation);
955            }
956            if (mListener != null) {
957                mListener.onAnimationStart(animation);
958            }
959        }
960
961        @Override
962        public void onAnimationCancel(Animator animation) {
963            if (mListener != null) {
964                mListener.onAnimationCancel(animation);
965            }
966            if (mAnimatorOnEndMap != null) {
967                mAnimatorOnEndMap.remove(animation);
968            }
969        }
970
971        @Override
972        public void onAnimationRepeat(Animator animation) {
973            if (mListener != null) {
974                mListener.onAnimationRepeat(animation);
975            }
976        }
977
978        @Override
979        public void onAnimationEnd(Animator animation) {
980            mView.setHasTransientState(false);
981            if (mListener != null) {
982                mListener.onAnimationEnd(animation);
983            }
984            if (mAnimatorOnEndMap != null) {
985                Runnable r = mAnimatorOnEndMap.get(animation);
986                if (r != null) {
987                    r.run();
988                }
989                mAnimatorOnEndMap.remove(animation);
990            }
991            if (mAnimatorCleanupMap != null) {
992                Runnable r = mAnimatorCleanupMap.get(animation);
993                if (r != null) {
994                    r.run();
995                }
996                mAnimatorCleanupMap.remove(animation);
997            }
998            mAnimatorMap.remove(animation);
999        }
1000
1001        /**
1002         * Calculate the current value for each property and set it on the view. Invalidate
1003         * the view object appropriately, depending on which properties are being animated.
1004         *
1005         * @param animation The animator associated with the properties that need to be
1006         * set. This animator holds the animation fraction which we will use to calculate
1007         * the current value of each property.
1008         */
1009        @Override
1010        public void onAnimationUpdate(ValueAnimator animation) {
1011            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
1012            if (propertyBundle == null) {
1013                // Shouldn't happen, but just to play it safe
1014                return;
1015            }
1016            boolean useDisplayListProperties = mView.mDisplayList != null;
1017
1018            // alpha requires slightly different treatment than the other (transform) properties.
1019            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
1020            // logic is dependent on how the view handles an internal call to onSetAlpha().
1021            // We track what kinds of properties are set, and how alpha is handled when it is
1022            // set, and perform the invalidation steps appropriately.
1023            boolean alphaHandled = false;
1024            if (!useDisplayListProperties) {
1025                mView.invalidateParentCaches();
1026            }
1027            float fraction = animation.getAnimatedFraction();
1028            int propertyMask = propertyBundle.mPropertyMask;
1029            if ((propertyMask & TRANSFORM_MASK) != 0) {
1030                mView.invalidateViewProperty(false, false);
1031            }
1032            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
1033            if (valueList != null) {
1034                int count = valueList.size();
1035                for (int i = 0; i < count; ++i) {
1036                    NameValuesHolder values = valueList.get(i);
1037                    float value = values.mFromValue + fraction * values.mDeltaValue;
1038                    if (values.mNameConstant == ALPHA) {
1039                        alphaHandled = mView.setAlphaNoInvalidation(value);
1040                    } else {
1041                        setValue(values.mNameConstant, value);
1042                    }
1043                }
1044            }
1045            if ((propertyMask & TRANSFORM_MASK) != 0) {
1046                mView.mTransformationInfo.mMatrixDirty = true;
1047                if (!useDisplayListProperties) {
1048                    mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
1049                }
1050            }
1051            // invalidate(false) in all cases except if alphaHandled gets set to true
1052            // via the call to setAlphaNoInvalidation(), above
1053            if (alphaHandled) {
1054                mView.invalidate(true);
1055            } else {
1056                mView.invalidateViewProperty(false, false);
1057            }
1058        }
1059    }
1060}
1061