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