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