1package android.support.v7.widget;
2
3import android.support.annotation.NonNull;
4import android.support.annotation.Nullable;
5import android.support.v7.widget.RecyclerView.Adapter;
6import android.support.v7.widget.RecyclerView.ViewHolder;
7import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
8import android.util.Log;
9import android.view.View;
10
11import 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 */
23abstract 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")
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     */
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
71    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
72        return !mSupportsChangeAnimations || viewHolder.isInvalid();
73    }
74
75    @Override
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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")
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     */
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")
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     */
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")
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     */
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")
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     */
440    public void onChangeFinished(ViewHolder item, boolean oldItem) {
441    }
442}
443