Home | History | Annotate | Download | only in widget
      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 
     17 package androidx.recyclerview.widget;
     18 
     19 import android.util.Log;
     20 import android.view.View;
     21 
     22 import androidx.annotation.NonNull;
     23 import androidx.annotation.Nullable;
     24 
     25 /**
     26  * A wrapper class for ItemAnimator that records View bounds and decides whether it should run
     27  * move, change, add or remove animations. This class also replicates the original ItemAnimator
     28  * API.
     29  * <p>
     30  * It uses {@link RecyclerView.ItemAnimator.ItemHolderInfo} to track the bounds information of the Views. If you would like
     31  * to
     32  * extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
     33  * class that extends {@link RecyclerView.ItemAnimator.ItemHolderInfo}.
     34  */
     35 public abstract class SimpleItemAnimator extends RecyclerView.ItemAnimator {
     36 
     37     private static final boolean DEBUG = false;
     38 
     39     private static final String TAG = "SimpleItemAnimator";
     40 
     41     boolean mSupportsChangeAnimations = true;
     42 
     43     /**
     44      * Returns whether this ItemAnimator supports animations of change events.
     45      *
     46      * @return true if change animations are supported, false otherwise
     47      */
     48     @SuppressWarnings("unused")
     49     public boolean getSupportsChangeAnimations() {
     50         return mSupportsChangeAnimations;
     51     }
     52 
     53     /**
     54      * Sets whether this ItemAnimator supports animations of item change events.
     55      * If you set this property to false, actions on the data set which change the
     56      * contents of items will not be animated. What those animations do is left
     57      * up to the discretion of the ItemAnimator subclass, in its
     58      * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} implementation.
     59      * The value of this property is true by default.
     60      *
     61      * @param supportsChangeAnimations true if change animations are supported by
     62      *                                 this ItemAnimator, false otherwise. If the property is false,
     63      *                                 the ItemAnimator
     64      *                                 will not receive a call to
     65      *                                 {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int,
     66      *                                 int)} when changes occur.
     67      * @see RecyclerView.Adapter#notifyItemChanged(int)
     68      * @see RecyclerView.Adapter#notifyItemRangeChanged(int, int)
     69      */
     70     public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
     71         mSupportsChangeAnimations = supportsChangeAnimations;
     72     }
     73 
     74     /**
     75      * {@inheritDoc}
     76      *
     77      * @return True if change animations are not supported or the ViewHolder is invalid,
     78      * false otherwise.
     79      *
     80      * @see #setSupportsChangeAnimations(boolean)
     81      */
     82     @Override
     83     public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
     84         return !mSupportsChangeAnimations || viewHolder.isInvalid();
     85     }
     86 
     87     @Override
     88     public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
     89             @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
     90         int oldLeft = preLayoutInfo.left;
     91         int oldTop = preLayoutInfo.top;
     92         View disappearingItemView = viewHolder.itemView;
     93         int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
     94         int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
     95         if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
     96             disappearingItemView.layout(newLeft, newTop,
     97                     newLeft + disappearingItemView.getWidth(),
     98                     newTop + disappearingItemView.getHeight());
     99             if (DEBUG) {
    100                 Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
    101             }
    102             return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
    103         } else {
    104             if (DEBUG) {
    105                 Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
    106             }
    107             return animateRemove(viewHolder);
    108         }
    109     }
    110 
    111     @Override
    112     public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder,
    113             @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    114         if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
    115                 || preLayoutInfo.top != postLayoutInfo.top)) {
    116             // slide items in if before/after locations differ
    117             if (DEBUG) {
    118                 Log.d(TAG, "APPEARING: " + viewHolder + " with view " + viewHolder);
    119             }
    120             return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
    121                     postLayoutInfo.left, postLayoutInfo.top);
    122         } else {
    123             if (DEBUG) {
    124                 Log.d(TAG, "ADDED: " + viewHolder + " with view " + viewHolder);
    125             }
    126             return animateAdd(viewHolder);
    127         }
    128     }
    129 
    130     @Override
    131     public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder,
    132             @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
    133         if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
    134             if (DEBUG) {
    135                 Log.d(TAG, "PERSISTENT: " + viewHolder
    136                         + " with view " + viewHolder.itemView);
    137             }
    138             return animateMove(viewHolder,
    139                     preInfo.left, preInfo.top, postInfo.left, postInfo.top);
    140         }
    141         dispatchMoveFinished(viewHolder);
    142         return false;
    143     }
    144 
    145     @Override
    146     public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder,
    147             @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
    148         if (DEBUG) {
    149             Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
    150         }
    151         final int fromLeft = preInfo.left;
    152         final int fromTop = preInfo.top;
    153         final int toLeft, toTop;
    154         if (newHolder.shouldIgnore()) {
    155             toLeft = preInfo.left;
    156             toTop = preInfo.top;
    157         } else {
    158             toLeft = postInfo.left;
    159             toTop = postInfo.top;
    160         }
    161         return animateChange(oldHolder, newHolder, fromLeft, fromTop, toLeft, toTop);
    162     }
    163 
    164     /**
    165      * Called when an item is removed from the RecyclerView. Implementors can choose
    166      * whether and how to animate that change, but must always call
    167      * {@link #dispatchRemoveFinished(RecyclerView.ViewHolder)} when done, either
    168      * immediately (if no animation will occur) or after the animation actually finishes.
    169      * The return value indicates whether an animation has been set up and whether the
    170      * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
    171      * next opportunity. This mechanism allows ItemAnimator to set up individual animations
    172      * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()},
    173      * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()},
    174      * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and
    175      * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one,
    176      * then start the animations together in the later call to {@link #runPendingAnimations()}.
    177      *
    178      * <p>This method may also be called for disappearing items which continue to exist in the
    179      * RecyclerView, but for which the system does not have enough information to animate
    180      * them out of view. In that case, the default animation for removing items is run
    181      * on those items as well.</p>
    182      *
    183      * @param holder The item that is being removed.
    184      * @return true if a later call to {@link #runPendingAnimations()} is requested,
    185      * false otherwise.
    186      */
    187     public abstract boolean animateRemove(RecyclerView.ViewHolder holder);
    188 
    189     /**
    190      * Called when an item is added to the RecyclerView. Implementors can choose
    191      * whether and how to animate that change, but must always call
    192      * {@link #dispatchAddFinished(RecyclerView.ViewHolder)} when done, either
    193      * immediately (if no animation will occur) or after the animation actually finishes.
    194      * The return value indicates whether an animation has been set up and whether the
    195      * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
    196      * next opportunity. This mechanism allows ItemAnimator to set up individual animations
    197      * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()},
    198      * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()},
    199      * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and
    200      * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one,
    201      * then start the animations together in the later call to {@link #runPendingAnimations()}.
    202      *
    203      * <p>This method may also be called for appearing items which were already in the
    204      * RecyclerView, but for which the system does not have enough information to animate
    205      * them into view. In that case, the default animation for adding items is run
    206      * on those items as well.</p>
    207      *
    208      * @param holder The item that is being added.
    209      * @return true if a later call to {@link #runPendingAnimations()} is requested,
    210      * false otherwise.
    211      */
    212     public abstract boolean animateAdd(RecyclerView.ViewHolder holder);
    213 
    214     /**
    215      * Called when an item is moved in the RecyclerView. Implementors can choose
    216      * whether and how to animate that change, but must always call
    217      * {@link #dispatchMoveFinished(RecyclerView.ViewHolder)} when done, either
    218      * immediately (if no animation will occur) or after the animation actually finishes.
    219      * The return value indicates whether an animation has been set up and whether the
    220      * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
    221      * next opportunity. This mechanism allows ItemAnimator to set up individual animations
    222      * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()},
    223      * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()},
    224      * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and
    225      * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one,
    226      * then start the animations together in the later call to {@link #runPendingAnimations()}.
    227      *
    228      * @param holder The item that is being moved.
    229      * @return true if a later call to {@link #runPendingAnimations()} is requested,
    230      * false otherwise.
    231      */
    232     public abstract boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY,
    233             int toX, int toY);
    234 
    235     /**
    236      * Called when an item is changed in the RecyclerView, as indicated by a call to
    237      * {@link RecyclerView.Adapter#notifyItemChanged(int)} or
    238      * {@link RecyclerView.Adapter#notifyItemRangeChanged(int, int)}.
    239      * <p>
    240      * Implementers can choose whether and how to animate changes, but must always call
    241      * {@link #dispatchChangeFinished(RecyclerView.ViewHolder, boolean)} for each non-null distinct ViewHolder,
    242      * either immediately (if no animation will occur) or after the animation actually finishes.
    243      * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call
    244      * {@link #dispatchChangeFinished(RecyclerView.ViewHolder, boolean)} once and only once. In that case, the
    245      * second parameter of {@code dispatchChangeFinished} is ignored.
    246      * <p>
    247      * The return value indicates whether an animation has been set up and whether the
    248      * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
    249      * next opportunity. This mechanism allows ItemAnimator to set up individual animations
    250      * as separate calls to {@link #animateAdd(RecyclerView.ViewHolder) animateAdd()},
    251      * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int) animateMove()},
    252      * {@link #animateRemove(RecyclerView.ViewHolder) animateRemove()}, and
    253      * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)} come in one by one,
    254      * then start the animations together in the later call to {@link #runPendingAnimations()}.
    255      *
    256      * @param oldHolder The original item that changed.
    257      * @param newHolder The new item that was created with the changed content. Might be null
    258      * @param fromLeft  Left of the old view holder
    259      * @param fromTop   Top of the old view holder
    260      * @param toLeft    Left of the new view holder
    261      * @param toTop     Top of the new view holder
    262      * @return true if a later call to {@link #runPendingAnimations()} is requested,
    263      * false otherwise.
    264      */
    265     public abstract boolean animateChange(RecyclerView.ViewHolder oldHolder,
    266             RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
    267 
    268     /**
    269      * Method to be called by subclasses when a remove animation is done.
    270      *
    271      * @param item The item which has been removed
    272      * @see RecyclerView.ItemAnimator#animateDisappearance(RecyclerView.ViewHolder, ItemHolderInfo,
    273      * ItemHolderInfo)
    274      */
    275     public final void dispatchRemoveFinished(RecyclerView.ViewHolder item) {
    276         onRemoveFinished(item);
    277         dispatchAnimationFinished(item);
    278     }
    279 
    280     /**
    281      * Method to be called by subclasses when a move animation is done.
    282      *
    283      * @param item The item which has been moved
    284      * @see RecyclerView.ItemAnimator#animateDisappearance(RecyclerView.ViewHolder, ItemHolderInfo,
    285      * ItemHolderInfo)
    286      * @see RecyclerView.ItemAnimator#animatePersistence(RecyclerView.ViewHolder, ItemHolderInfo, ItemHolderInfo)
    287      *
    288      * @see RecyclerView.ItemAnimator#animateAppearance(RecyclerView.ViewHolder, ItemHolderInfo, ItemHolderInfo)
    289      */
    290     public final void dispatchMoveFinished(RecyclerView.ViewHolder item) {
    291         onMoveFinished(item);
    292         dispatchAnimationFinished(item);
    293     }
    294 
    295     /**
    296      * Method to be called by subclasses when an add animation is done.
    297      *
    298      * @param item The item which has been added
    299      */
    300     public final void dispatchAddFinished(RecyclerView.ViewHolder item) {
    301         onAddFinished(item);
    302         dispatchAnimationFinished(item);
    303     }
    304 
    305     /**
    306      * Method to be called by subclasses when a change animation is done.
    307      *
    308      * @param item    The item which has been changed (this method must be called for
    309      *                each non-null ViewHolder passed into
    310      *                {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}).
    311      * @param oldItem true if this is the old item that was changed, false if
    312      *                it is the new item that replaced the old item.
    313      * @see #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)
    314      */
    315     public final void dispatchChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
    316         onChangeFinished(item, oldItem);
    317         dispatchAnimationFinished(item);
    318     }
    319 
    320     /**
    321      * Method to be called by subclasses when a remove animation is being started.
    322      *
    323      * @param item The item being removed
    324      */
    325     public final void dispatchRemoveStarting(RecyclerView.ViewHolder item) {
    326         onRemoveStarting(item);
    327     }
    328 
    329     /**
    330      * Method to be called by subclasses when a move animation is being started.
    331      *
    332      * @param item The item being moved
    333      */
    334     public final void dispatchMoveStarting(RecyclerView.ViewHolder item) {
    335         onMoveStarting(item);
    336     }
    337 
    338     /**
    339      * Method to be called by subclasses when an add animation is being started.
    340      *
    341      * @param item The item being added
    342      */
    343     public final void dispatchAddStarting(RecyclerView.ViewHolder item) {
    344         onAddStarting(item);
    345     }
    346 
    347     /**
    348      * Method to be called by subclasses when a change animation is being started.
    349      *
    350      * @param item    The item which has been changed (this method must be called for
    351      *                each non-null ViewHolder passed into
    352      *                {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}).
    353      * @param oldItem true if this is the old item that was changed, false if
    354      *                it is the new item that replaced the old item.
    355      */
    356     public final void dispatchChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
    357         onChangeStarting(item, oldItem);
    358     }
    359 
    360     /**
    361      * Called when a remove animation is being started on the given ViewHolder.
    362      * The default implementation does nothing. Subclasses may wish to override
    363      * this method to handle any ViewHolder-specific operations linked to animation
    364      * lifecycles.
    365      *
    366      * @param item The ViewHolder being animated.
    367      */
    368     @SuppressWarnings("UnusedParameters")
    369     public void onRemoveStarting(RecyclerView.ViewHolder item) {
    370     }
    371 
    372     /**
    373      * Called when a remove animation has ended on the given ViewHolder.
    374      * The default implementation does nothing. Subclasses may wish to override
    375      * this method to handle any ViewHolder-specific operations linked to animation
    376      * lifecycles.
    377      *
    378      * @param item The ViewHolder being animated.
    379      */
    380     public void onRemoveFinished(RecyclerView.ViewHolder item) {
    381     }
    382 
    383     /**
    384      * Called when an add animation is being started on the given ViewHolder.
    385      * The default implementation does nothing. Subclasses may wish to override
    386      * this method to handle any ViewHolder-specific operations linked to animation
    387      * lifecycles.
    388      *
    389      * @param item The ViewHolder being animated.
    390      */
    391     @SuppressWarnings("UnusedParameters")
    392     public void onAddStarting(RecyclerView.ViewHolder item) {
    393     }
    394 
    395     /**
    396      * Called when an add animation has ended on the given ViewHolder.
    397      * The default implementation does nothing. Subclasses may wish to override
    398      * this method to handle any ViewHolder-specific operations linked to animation
    399      * lifecycles.
    400      *
    401      * @param item The ViewHolder being animated.
    402      */
    403     public void onAddFinished(RecyclerView.ViewHolder item) {
    404     }
    405 
    406     /**
    407      * Called when a move animation is being started on the given ViewHolder.
    408      * The default implementation does nothing. Subclasses may wish to override
    409      * this method to handle any ViewHolder-specific operations linked to animation
    410      * lifecycles.
    411      *
    412      * @param item The ViewHolder being animated.
    413      */
    414     @SuppressWarnings("UnusedParameters")
    415     public void onMoveStarting(RecyclerView.ViewHolder item) {
    416     }
    417 
    418     /**
    419      * Called when a move animation has ended on the given ViewHolder.
    420      * The default implementation does nothing. Subclasses may wish to override
    421      * this method to handle any ViewHolder-specific operations linked to animation
    422      * lifecycles.
    423      *
    424      * @param item The ViewHolder being animated.
    425      */
    426     public void onMoveFinished(RecyclerView.ViewHolder item) {
    427     }
    428 
    429     /**
    430      * Called when a change animation is being started on the given ViewHolder.
    431      * The default implementation does nothing. Subclasses may wish to override
    432      * this method to handle any ViewHolder-specific operations linked to animation
    433      * lifecycles.
    434      *
    435      * @param item    The ViewHolder being animated.
    436      * @param oldItem true if this is the old item that was changed, false if
    437      *                it is the new item that replaced the old item.
    438      */
    439     @SuppressWarnings("UnusedParameters")
    440     public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
    441     }
    442 
    443     /**
    444      * Called when a change animation has ended on the given ViewHolder.
    445      * The default implementation does nothing. Subclasses may wish to override
    446      * this method to handle any ViewHolder-specific operations linked to animation
    447      * lifecycles.
    448      *
    449      * @param item    The ViewHolder being animated.
    450      * @param oldItem true if this is the old item that was changed, false if
    451      *                it is the new item that replaced the old item.
    452      */
    453     public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
    454     }
    455 }
    456 
    457