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