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