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