Home | History | Annotate | Download | only in view
      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 
     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