1 /*
2  * Copyright 2018 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 package androidx.core.view;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ValueAnimator;
21 import android.os.Build;
22 import android.view.View;
23 import android.view.animation.Interpolator;
24 
25 import java.lang.ref.WeakReference;
26 
27 public 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 
ViewPropertyAnimatorCompat(View view)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 
ViewPropertyAnimatorListenerApi14(ViewPropertyAnimatorCompat vpa)45         ViewPropertyAnimatorListenerApi14(ViewPropertyAnimatorCompat vpa) {
46             mVpa = vpa;
47         }
48 
49         @Override
onAnimationStart(View view)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
onAnimationEnd(View view)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
onAnimationCancel(View view)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      */
setDuration(long value)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      */
alpha(float value)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      */
alphaBy(float value)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      */
translationX(float value)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      */
translationY(float value)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      */
withEndAction(Runnable runnable)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      */
getDuration()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      */
setInterpolator(Interpolator value)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      */
getInterpolator()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      */
setStartDelay(long value)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      */
getStartDelay()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      */
rotation(float value)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      */
rotationBy(float value)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      */
rotationX(float value)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      */
rotationXBy(float value)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      */
rotationY(float value)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      */
rotationYBy(float value)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      */
scaleX(float value)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      */
scaleXBy(float value)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      */
scaleY(float value)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      */
scaleYBy(float value)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      */
cancel()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      */
x(float value)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      */
xBy(float value)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      */
y(float value)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      */
yBy(float value)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      */
translationXBy(float value)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      */
translationYBy(float value)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      */
translationZBy(float value)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      */
translationZ(float value)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      */
z(float value)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      */
zBy(float value)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      */
start()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      */
withLayer()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      */
withStartAction(Runnable runnable)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      */
setListener(final ViewPropertyAnimatorListener listener)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 
setListenerInternal(final View view, final ViewPropertyAnimatorListener listener)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      */
setUpdateListener( final ViewPropertyAnimatorUpdateListener listener)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