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