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