1/*
2 * Copyright (C) 2014 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 */
16package android.support.v4.view;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.ValueAnimator;
21import android.os.Build;
22import android.view.View;
23import android.view.animation.Interpolator;
24
25import java.lang.ref.WeakReference;
26
27public final class ViewPropertyAnimatorCompat {
28    private static final String TAG = "ViewAnimatorCompat";
29    private WeakReference<View> mView;
30    Runnable mStartAction = null;
31    Runnable mEndAction = null;
32    int mOldLayerType = -1;
33    // HACK ALERT! Choosing this id knowing that the framework does not use it anywhere
34    // internally and apps should use ids higher than it
35    static final int LISTENER_TAG_ID = 0x7e000000;
36
37    ViewPropertyAnimatorCompat(View view) {
38        mView = new WeakReference<View>(view);
39    }
40
41    static class ViewPropertyAnimatorListenerApi14 implements ViewPropertyAnimatorListener {
42        ViewPropertyAnimatorCompat mVpa;
43        boolean mAnimEndCalled;
44
45        ViewPropertyAnimatorListenerApi14(ViewPropertyAnimatorCompat vpa) {
46            mVpa = vpa;
47        }
48
49        @Override
50        public void onAnimationStart(View view) {
51            // Reset our end called flag, since this is a new animation...
52            mAnimEndCalled = false;
53
54            if (mVpa.mOldLayerType > -1) {
55                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
56            }
57            if (mVpa.mStartAction != null) {
58                Runnable startAction = mVpa.mStartAction;
59                mVpa.mStartAction = null;
60                startAction.run();
61            }
62            Object listenerTag = view.getTag(LISTENER_TAG_ID);
63            ViewPropertyAnimatorListener listener = null;
64            if (listenerTag instanceof ViewPropertyAnimatorListener) {
65                listener = (ViewPropertyAnimatorListener) listenerTag;
66            }
67            if (listener != null) {
68                listener.onAnimationStart(view);
69            }
70        }
71
72        @Override
73        public void onAnimationEnd(View view) {
74            if (mVpa.mOldLayerType > -1) {
75                view.setLayerType(mVpa.mOldLayerType, null);
76                mVpa.mOldLayerType = -1;
77            }
78            if (Build.VERSION.SDK_INT >= 16 || !mAnimEndCalled) {
79                // Pre-v16 seems to have a bug where onAnimationEnd is called
80                // twice, therefore we only dispatch on the first call
81                if (mVpa.mEndAction != null) {
82                    Runnable endAction = mVpa.mEndAction;
83                    mVpa.mEndAction = null;
84                    endAction.run();
85                }
86                Object listenerTag = view.getTag(LISTENER_TAG_ID);
87                ViewPropertyAnimatorListener listener = null;
88                if (listenerTag instanceof ViewPropertyAnimatorListener) {
89                    listener = (ViewPropertyAnimatorListener) listenerTag;
90                }
91                if (listener != null) {
92                    listener.onAnimationEnd(view);
93                }
94                mAnimEndCalled = true;
95            }
96        }
97
98        @Override
99        public void onAnimationCancel(View view) {
100            Object listenerTag = view.getTag(LISTENER_TAG_ID);
101            ViewPropertyAnimatorListener listener = null;
102            if (listenerTag instanceof ViewPropertyAnimatorListener) {
103                listener = (ViewPropertyAnimatorListener) listenerTag;
104            }
105            if (listener != null) {
106                listener.onAnimationCancel(view);
107            }
108        }
109    }
110
111    /**
112     * Sets the duration for the underlying animator that animates the requested properties.
113     * By default, the animator uses the default value for ValueAnimator. Calling this method
114     * will cause the declared value to be used instead.
115     *
116     * @param value The length of ensuing property animations, in milliseconds. The value
117     * cannot be negative.
118     * @return This object, allowing calls to methods in this class to be chained.
119     */
120    public ViewPropertyAnimatorCompat setDuration(long value) {
121        View view;
122        if ((view = mView.get()) != null) {
123            view.animate().setDuration(value);
124        }
125        return this;
126    }
127
128    /**
129     * This method will cause the View's <code>alpha</code> property to be animated to the
130     * specified value. Animations already running on the property will be canceled.
131     *
132     * @param value The value to be animated to.
133     * @return This object, allowing calls to methods in this class to be chained.
134     */
135    public ViewPropertyAnimatorCompat alpha(float value) {
136        View view;
137        if ((view = mView.get()) != null) {
138            view.animate().alpha(value);
139        }
140        return this;
141    }
142
143    /**
144     * This method will cause the View's <code>alpha</code> property to be animated by the
145     * specified value. Animations already running on the property will be canceled.
146     *
147     * @param value The amount to be animated by, as an offset from the current value.
148     * @return This object, allowing calls to methods in this class to be chained.
149     */
150    public ViewPropertyAnimatorCompat alphaBy(float value) {
151        View view;
152        if ((view = mView.get()) != null) {
153            view.animate().alphaBy(value);
154        }
155        return this;
156    }
157
158    /**
159     * This method will cause the View's <code>translationX</code> property to be animated to the
160     * specified value. Animations already running on the property will be canceled.
161     *
162     * @param value The value to be animated to.
163     * @return This object, allowing calls to methods in this class to be chained.
164     */
165    public ViewPropertyAnimatorCompat translationX(float value) {
166        View view;
167        if ((view = mView.get()) != null) {
168            view.animate().translationX(value);
169        }
170        return this;
171    }
172
173    /**
174     * This method will cause the View's <code>translationY</code> property to be animated to the
175     * specified value. Animations already running on the property will be canceled.
176     *
177     * @param value The value to be animated to.
178     * @return This object, allowing calls to methods in this class to be chained.
179     */
180    public ViewPropertyAnimatorCompat translationY(float value) {
181        View view;
182        if ((view = mView.get()) != null) {
183            view.animate().translationY(value);
184        }
185        return this;
186    }
187
188    /**
189     * Specifies an action to take place when the next animation ends. The action is only
190     * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
191     * that animation, the runnable will not run.
192     * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
193     * choreographing ViewPropertyAnimator animations with other animations or actions
194     * in the application.
195     *
196     * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
197     * <pre>
198     *     Runnable endAction = new Runnable() {
199     *         public void run() {
200     *             view.animate().x(0);
201     *         }
202     *     };
203     *     view.animate().x(200).withEndAction(endAction);
204     * </pre>
205     *
206     * <p>For API 14 and 15, this method will run by setting
207     * a listener on the ViewPropertyAnimatorCompat object and running the action
208     * in that listener's {@link ViewPropertyAnimatorListener#onAnimationEnd(View)} method.</p>
209     *
210     * @param runnable The action to run when the next animation ends.
211     * @return This object, allowing calls to methods in this class to be chained.
212     */
213    public ViewPropertyAnimatorCompat withEndAction(Runnable runnable) {
214        View view;
215        if ((view = mView.get()) != null) {
216            if (Build.VERSION.SDK_INT >= 16) {
217                view.animate().withEndAction(runnable);
218            } else {
219                setListenerInternal(view, new ViewPropertyAnimatorListenerApi14(this));
220                mEndAction = runnable;
221            }
222        }
223        return this;
224    }
225
226    /**
227     * Returns the current duration of property animations. If the duration was set on this
228     * object, that value is returned. Otherwise, the default value of the underlying Animator
229     * is returned.
230     *
231     * @see #setDuration(long)
232     * @return The duration of animations, in milliseconds.
233     */
234    public long getDuration() {
235        View view;
236        if ((view = mView.get()) != null) {
237            return view.animate().getDuration();
238        } else {
239            return 0;
240        }
241    }
242
243    /**
244     * Sets the interpolator for the underlying animator that animates the requested properties.
245     * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
246     * will cause the declared object to be used instead.
247     *
248     * @param value The TimeInterpolator to be used for ensuing property animations.
249     * @return This object, allowing calls to methods in this class to be chained.
250     */
251    public ViewPropertyAnimatorCompat setInterpolator(Interpolator value) {
252        View view;
253        if ((view = mView.get()) != null) {
254            view.animate().setInterpolator(value);
255        }
256        return this;
257    }
258
259    /**
260     * Returns the timing interpolator that this animation uses.
261     *
262     * @return The timing interpolator for this animation.
263     */
264    public Interpolator getInterpolator() {
265        View view;
266        if ((view = mView.get()) != null) {
267            if (Build.VERSION.SDK_INT >= 18) {
268                return (Interpolator) view.animate().getInterpolator();
269            }
270        }
271        return null;
272    }
273
274    /**
275     * Sets the startDelay for the underlying animator that animates the requested properties.
276     * By default, the animator uses the default value for ValueAnimator. Calling this method
277     * will cause the declared value to be used instead.
278     *
279     * @param value The delay of ensuing property animations, in milliseconds. The value
280     * cannot be negative.
281     * @return This object, allowing calls to methods in this class to be chained.
282     */
283    public ViewPropertyAnimatorCompat setStartDelay(long value) {
284        View view;
285        if ((view = mView.get()) != null) {
286            view.animate().setStartDelay(value);
287        }
288        return this;
289    }
290
291    /**
292     * Returns the current startDelay of property animations. If the startDelay was set on this
293     * object, that value is returned. Otherwise, the default value of the underlying Animator
294     * is returned.
295     *
296     * @see #setStartDelay(long)
297     * @return The startDelay of animations, in milliseconds.
298     */
299    public long getStartDelay() {
300        View view;
301        if ((view = mView.get()) != null) {
302            return view.animate().getStartDelay();
303        } else {
304            return 0;
305        }
306    }
307
308    /**
309     * This method will cause the View's <code>rotation</code> property to be animated to the
310     * specified value. Animations already running on the property will be canceled.
311     *
312     * @param value The value to be animated to.
313     * @return This object, allowing calls to methods in this class to be chained.
314     */
315    public ViewPropertyAnimatorCompat rotation(float value) {
316        View view;
317        if ((view = mView.get()) != null) {
318            view.animate().rotation(value);
319        }
320        return this;
321    }
322
323    /**
324     * This method will cause the View's <code>rotation</code> property to be animated by the
325     * specified value. Animations already running on the property will be canceled.
326     *
327     * @param value The amount to be animated by, as an offset from the current value.
328     * @return This object, allowing calls to methods in this class to be chained.
329     */
330    public ViewPropertyAnimatorCompat rotationBy(float value) {
331        View view;
332        if ((view = mView.get()) != null) {
333            view.animate().rotationBy(value);
334        }
335        return this;
336    }
337
338    /**
339     * This method will cause the View's <code>rotationX</code> property to be animated to the
340     * specified value. Animations already running on the property will be canceled.
341     *
342     * @param value The value to be animated to.
343     * @return This object, allowing calls to methods in this class to be chained.
344     */
345    public ViewPropertyAnimatorCompat rotationX(float value) {
346        View view;
347        if ((view = mView.get()) != null) {
348            view.animate().rotationX(value);
349        }
350        return this;
351    }
352
353    /**
354     * This method will cause the View's <code>rotationX</code> property to be animated by the
355     * specified value. Animations already running on the property will be canceled.
356     *
357     * @param value The amount to be animated by, as an offset from the current value.
358     * @return This object, allowing calls to methods in this class to be chained.
359     */
360    public ViewPropertyAnimatorCompat rotationXBy(float value) {
361        View view;
362        if ((view = mView.get()) != null) {
363            view.animate().rotationXBy(value);
364        }
365        return this;
366    }
367
368    /**
369     * This method will cause the View's <code>rotationY</code> property to be animated to the
370     * specified value. Animations already running on the property will be canceled.
371     *
372     * @param value The value to be animated to.
373     * @return This object, allowing calls to methods in this class to be chained.
374     */
375    public ViewPropertyAnimatorCompat rotationY(float value) {
376        View view;
377        if ((view = mView.get()) != null) {
378            view.animate().rotationY(value);
379        }
380        return this;
381    }
382
383    /**
384     * This method will cause the View's <code>rotationY</code> property to be animated by the
385     * specified value. Animations already running on the property will be canceled.
386     *
387     * @param value The amount to be animated by, as an offset from the current value.
388     * @return This object, allowing calls to methods in this class to be chained.
389     */
390    public ViewPropertyAnimatorCompat rotationYBy(float value) {
391        View view;
392        if ((view = mView.get()) != null) {
393            view.animate().rotationYBy(value);
394        }
395        return this;
396    }
397
398    /**
399     * This method will cause the View's <code>scaleX</code> property to be animated to the
400     * specified value. Animations already running on the property will be canceled.
401     *
402     * @param value The value to be animated to.
403     * @return This object, allowing calls to methods in this class to be chained.
404     */
405    public ViewPropertyAnimatorCompat scaleX(float value) {
406        View view;
407        if ((view = mView.get()) != null) {
408            view.animate().scaleX(value);
409        }
410        return this;
411    }
412
413    /**
414     * This method will cause the View's <code>scaleX</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     * @return This object, allowing calls to methods in this class to be chained.
419     */
420    public ViewPropertyAnimatorCompat scaleXBy(float value) {
421        View view;
422        if ((view = mView.get()) != null) {
423            view.animate().scaleXBy(value);
424        }
425        return this;
426    }
427
428    /**
429     * This method will cause the View's <code>scaleY</code> property to be animated to the
430     * specified value. Animations already running on the property will be canceled.
431     *
432     * @param value The value to be animated to.
433     * @return This object, allowing calls to methods in this class to be chained.
434     */
435    public ViewPropertyAnimatorCompat scaleY(float value) {
436        View view;
437        if ((view = mView.get()) != null) {
438            view.animate().scaleY(value);
439        }
440        return this;
441    }
442
443    /**
444     * This method will cause the View's <code>scaleY</code> property to be animated by the
445     * specified value. Animations already running on the property will be canceled.
446     *
447     * @param value The amount to be animated by, as an offset from the current value.
448     * @return This object, allowing calls to methods in this class to be chained.
449     */
450    public ViewPropertyAnimatorCompat scaleYBy(float value) {
451        View view;
452        if ((view = mView.get()) != null) {
453            view.animate().scaleYBy(value);
454        }
455        return this;
456    }
457
458    /**
459     * Cancels all property animations that are currently running or pending.
460     */
461    public void cancel() {
462        View view;
463        if ((view = mView.get()) != null) {
464            view.animate().cancel();
465        }
466    }
467
468    /**
469     * This method will cause the View's <code>x</code> property to be animated to the
470     * specified value. Animations already running on the property will be canceled.
471     *
472     * @param value The value to be animated to.
473     * @return This object, allowing calls to methods in this class to be chained.
474     */
475    public ViewPropertyAnimatorCompat x(float value) {
476        View view;
477        if ((view = mView.get()) != null) {
478            view.animate().x(value);
479        }
480        return this;
481    }
482
483    /**
484     * This method will cause the View's <code>x</code> property to be animated by the
485     * specified value. Animations already running on the property will be canceled.
486     *
487     * @param value The amount to be animated by, as an offset from the current value.
488     * @return This object, allowing calls to methods in this class to be chained.
489     */
490    public ViewPropertyAnimatorCompat xBy(float value) {
491        View view;
492        if ((view = mView.get()) != null) {
493            view.animate().xBy(value);
494        }
495        return this;
496    }
497
498    /**
499     * This method will cause the View's <code>y</code> property to be animated to the
500     * specified value. Animations already running on the property will be canceled.
501     *
502     * @param value The value to be animated to.
503     * @return This object, allowing calls to methods in this class to be chained.
504     */
505    public ViewPropertyAnimatorCompat y(float value) {
506        View view;
507        if ((view = mView.get()) != null) {
508            view.animate().y(value);
509        }
510        return this;
511    }
512
513    /**
514     * This method will cause the View's <code>y</code> property to be animated by the
515     * specified value. Animations already running on the property will be canceled.
516     *
517     * @param value The amount to be animated by, as an offset from the current value.
518     * @return This object, allowing calls to methods in this class to be chained.
519     */
520    public ViewPropertyAnimatorCompat yBy(float value) {
521        View view;
522        if ((view = mView.get()) != null) {
523            view.animate().yBy(value);
524        }
525        return this;
526    }
527
528    /**
529     * This method will cause the View's <code>translationX</code> property to be animated by the
530     * specified value. Animations already running on the property will be canceled.
531     *
532     * @param value The amount to be animated by, as an offset from the current value.
533     * @return This object, allowing calls to methods in this class to be chained.
534     */
535    public ViewPropertyAnimatorCompat translationXBy(float value) {
536        View view;
537        if ((view = mView.get()) != null) {
538            view.animate().translationXBy(value);
539        }
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     * @return This object, allowing calls to methods in this class to be chained.
549     */
550    public ViewPropertyAnimatorCompat translationYBy(float value) {
551        View view;
552        if ((view = mView.get()) != null) {
553            view.animate().translationYBy(value);
554        }
555        return this;
556    }
557
558    /**
559     * This method will cause the View's <code>translationZ</code> property to be animated by the
560     * specified value. Animations already running on the property will be canceled.
561     *
562     * <p>Prior to API 21, this method will do nothing.</p>
563     *
564     * @param value The amount to be animated by, as an offset from the current value.
565     * @return This object, allowing calls to methods in this class to be chained.
566     */
567    public ViewPropertyAnimatorCompat translationZBy(float value) {
568        View view;
569        if ((view = mView.get()) != null) {
570            if (Build.VERSION.SDK_INT >= 21) {
571                view.animate().translationZBy(value);
572            }
573        }
574        return this;
575    }
576
577    /**
578     * This method will cause the View's <code>translationZ</code> property to be animated to the
579     * specified value. Animations already running on the property will be canceled.
580     *
581     * <p>Prior to API 21, this method will do nothing.</p>
582     *
583     * @param value The amount to be animated by, as an offset from the current value.
584     * @return This object, allowing calls to methods in this class to be chained.
585     */
586    public ViewPropertyAnimatorCompat translationZ(float value) {
587        View view;
588        if ((view = mView.get()) != null) {
589            if (Build.VERSION.SDK_INT >= 21) {
590                view.animate().translationZ(value);
591            }
592        }
593        return this;
594    }
595
596    /**
597     * This method will cause the View's <code>z</code> property to be animated to the
598     * specified value. Animations already running on the property will be canceled.
599     *
600     * <p>Prior to API 21, this method will do nothing.</p>
601     *
602     * @param value The amount to be animated by, as an offset from the current value.
603     * @return This object, allowing calls to methods in this class to be chained.
604     */
605    public ViewPropertyAnimatorCompat z(float value) {
606        View view;
607        if ((view = mView.get()) != null) {
608            if (Build.VERSION.SDK_INT >= 21) {
609                view.animate().z(value);
610            }
611        }
612        return this;
613    }
614
615    /**
616     * This method will cause the View's <code>z</code> property to be animated by the
617     * specified value. Animations already running on the property will be canceled.
618     *
619     * <p>Prior to API 21, this method will do nothing.</p>
620     *
621     * @param value The amount to be animated by, as an offset from the current value.
622     * @return This object, allowing calls to methods in this class to be chained.
623     */
624    public ViewPropertyAnimatorCompat zBy(float value) {
625        View view;
626        if ((view = mView.get()) != null) {
627            if (Build.VERSION.SDK_INT >= 21) {
628                view.animate().zBy(value);
629            }
630        }
631        return this;
632    }
633
634    /**
635     * Starts the currently pending property animations immediately. Calling <code>start()</code>
636     * is optional because all animations start automatically at the next opportunity. However,
637     * if the animations are needed to start immediately and synchronously (not at the time when
638     * the next event is processed by the hierarchy, which is when the animations would begin
639     * otherwise), then this method can be used.
640     */
641    public void start() {
642        View view;
643        if ((view = mView.get()) != null) {
644            view.animate().start();
645        }
646    }
647
648    /**
649     * The View associated with this ViewPropertyAnimator will have its
650     * {@link View#setLayerType(int, android.graphics.Paint) layer type} set to
651     * {@link View#LAYER_TYPE_HARDWARE} for the duration of the next animation.
652     * As stated in the documentation for {@link View#LAYER_TYPE_HARDWARE},
653     * the actual type of layer used internally depends on the runtime situation of the
654     * view. If the activity and this view are hardware-accelerated, then the layer will be
655     * accelerated as well. If the activity or the view is not accelerated, then the layer will
656     * effectively be the same as {@link View#LAYER_TYPE_SOFTWARE}.
657     *
658     * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
659     * layer type of the View will be restored when the animation ends to what it was when this
660     * method was called, and this setting on ViewPropertyAnimator is only valid for the next
661     * animation. Note that calling this method and then independently setting the layer type of
662     * the View (by a direct call to
663     * {@link View#setLayerType(int, android.graphics.Paint)}) will result in some
664     * inconsistency, including having the layer type restored to its pre-withLayer()
665     * value when the animation ends.</p>
666     *
667     * <p>For API 14 and 15, this method will run by setting
668     * a listener on the ViewPropertyAnimatorCompat object, setting a hardware layer in
669     * the listener's {@link ViewPropertyAnimatorListener#onAnimationStart(View)} method,
670     * and then restoring the orignal layer type in the listener's
671     * {@link ViewPropertyAnimatorListener#onAnimationEnd(View)} method.</p>
672     *
673     * @see View#setLayerType(int, android.graphics.Paint)
674     * @return This object, allowing calls to methods in this class to be chained.
675     */
676    public ViewPropertyAnimatorCompat withLayer() {
677        View view;
678        if ((view = mView.get()) != null) {
679            if (Build.VERSION.SDK_INT >= 16) {
680                view.animate().withLayer();
681            } else {
682                mOldLayerType = view.getLayerType();
683                setListenerInternal(view, new ViewPropertyAnimatorListenerApi14(this));
684            }
685        }
686        return this;
687    }
688
689    /**
690     * Specifies an action to take place when the next animation runs. If there is a
691     * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
692     * action will run after that startDelay expires, when the actual animation begins.
693     * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
694     * choreographing ViewPropertyAnimator animations with other animations or actions
695     * in the application.
696     *
697     * <p>For API 14 and 15, this method will run by setting
698     * a listener on the ViewPropertyAnimatorCompat object and running the action
699     * in that listener's {@link ViewPropertyAnimatorListener#onAnimationStart(View)} method.</p>
700     *
701     * @param runnable The action to run when the next animation starts.
702     * @return This object, allowing calls to methods in this class to be chained.
703     */
704    public ViewPropertyAnimatorCompat withStartAction(Runnable runnable) {
705        View view;
706        if ((view = mView.get()) != null) {
707            if (Build.VERSION.SDK_INT >= 16) {
708                view.animate().withStartAction(runnable);
709            } else {
710                setListenerInternal(view, new ViewPropertyAnimatorListenerApi14(this));
711                mStartAction = runnable;
712            }
713        }
714        return this;
715    }
716
717    /**
718     * Sets a listener for events in the underlying Animators that run the property
719     * animations.
720     *
721     * @param listener The listener to be called with AnimatorListener events. A value of
722     * <code>null</code> removes any existing listener.
723     * @return This object, allowing calls to methods in this class to be chained.
724     */
725    public ViewPropertyAnimatorCompat setListener(final ViewPropertyAnimatorListener listener) {
726        final View view;
727        if ((view = mView.get()) != null) {
728            if (Build.VERSION.SDK_INT >= 16) {
729                setListenerInternal(view, listener);
730            } else {
731                view.setTag(LISTENER_TAG_ID, listener);
732                setListenerInternal(view, new ViewPropertyAnimatorListenerApi14(this));
733            }
734        }
735        return this;
736    }
737
738    private void setListenerInternal(final View view, final ViewPropertyAnimatorListener listener) {
739        if (listener != null) {
740            view.animate().setListener(new AnimatorListenerAdapter() {
741                @Override
742                public void onAnimationCancel(Animator animation) {
743                    listener.onAnimationCancel(view);
744                }
745
746                @Override
747                public void onAnimationEnd(Animator animation) {
748                    listener.onAnimationEnd(view);
749                }
750
751                @Override
752                public void onAnimationStart(Animator animation) {
753                    listener.onAnimationStart(view);
754                }
755            });
756        } else {
757            view.animate().setListener(null);
758        }
759    }
760
761    /**
762     * Sets a listener for update events in the underlying Animator that runs
763     * the property animations.
764     *
765     * <p>Prior to API 19, this method will do nothing.</p>
766     *
767     * @param listener The listener to be called with update events. A value of
768     * <code>null</code> removes any existing listener.
769     * @return This object, allowing calls to methods in this class to be chained.
770     */
771    public ViewPropertyAnimatorCompat setUpdateListener(
772            final ViewPropertyAnimatorUpdateListener listener) {
773        final View view;
774        if ((view = mView.get()) != null) {
775            if (Build.VERSION.SDK_INT >= 19) {
776                ValueAnimator.AnimatorUpdateListener wrapped = null;
777                if (listener != null) {
778                    wrapped = new ValueAnimator.AnimatorUpdateListener() {
779                        @Override
780                        public void onAnimationUpdate(ValueAnimator valueAnimator) {
781                            listener.onAnimationUpdate(view);
782                        }
783                    };
784                }
785                view.animate().setUpdateListener(wrapped);
786            }
787        }
788        return this;
789    }
790}
791