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")
getSupportsChangeAnimations()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      */
setSupportsChangeAnimations(boolean supportsChangeAnimations)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
canReuseUpdatedViewHolder(@onNull RecyclerView.ViewHolder viewHolder)85     public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
86         return !mSupportsChangeAnimations || viewHolder.isInvalid();
87     }
88 
89     @Override
animateDisappearance(@onNull ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo)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
animateAppearance(@onNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)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
animatePersistence(@onNull ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo)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
animateChange(@onNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo)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      */
animateRemove(ViewHolder holder)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      */
animateAdd(ViewHolder holder)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      */
animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY)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      */
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)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      */
dispatchRemoveFinished(ViewHolder item)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      */
dispatchMoveFinished(ViewHolder item)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      */
dispatchAddFinished(ViewHolder item)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      */
dispatchChangeFinished(ViewHolder item, boolean oldItem)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      */
dispatchRemoveStarting(ViewHolder item)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      */
dispatchMoveStarting(ViewHolder item)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      */
dispatchAddStarting(ViewHolder item)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      */
dispatchChangeStarting(ViewHolder item, boolean oldItem)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")
onRemoveStarting(ViewHolder item)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      */
onRemoveFinished(ViewHolder item)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")
onAddStarting(ViewHolder item)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      */
onAddFinished(ViewHolder item)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")
onMoveStarting(ViewHolder item)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      */
onMoveFinished(ViewHolder item)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")
onChangeStarting(ViewHolder item, boolean oldItem)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      */
onChangeFinished(ViewHolder item, boolean oldItem)454     public void onChangeFinished(ViewHolder item, boolean oldItem) {
455     }
456 }
457 
458