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") 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 */ 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 83 public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { 84 return !mSupportsChangeAnimations || viewHolder.isInvalid(); 85 } 86 87 @Override 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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") 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 */ 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") 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 */ 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") 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 */ 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") 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 */ 453 public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) { 454 } 455 } 456 457