DefaultItemAnimator.java revision c35968d173f900d8024bdf38174e2225c9a7f311
1/* 2 * Copyright (C) 2014 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 */ 16package android.support.v7.widget; 17 18import android.support.v4.view.ViewCompat; 19import android.support.v4.view.ViewPropertyAnimatorListener; 20import android.support.v7.widget.RecyclerView.ViewHolder; 21import android.view.View; 22 23import java.util.ArrayList; 24 25/** 26 * This implementation of {@link RecyclerView.ItemAnimator} provides basic 27 * animations on remove, add, and move events that happen to the items in 28 * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default. 29 * 30 * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator) 31 */ 32public class DefaultItemAnimator extends RecyclerView.ItemAnimator { 33 34 private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>(); 35 private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>(); 36 private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>(); 37 38 private ArrayList<ViewHolder> mAdditions = new ArrayList<ViewHolder>(); 39 private ArrayList<MoveInfo> mMoves = new ArrayList<MoveInfo>(); 40 41 private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>(); 42 private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>(); 43 private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>(); 44 45 private static class MoveInfo { 46 public ViewHolder holder; 47 public int fromX, fromY, toX, toY; 48 49 private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { 50 this.holder = holder; 51 this.fromX = fromX; 52 this.fromY = fromY; 53 this.toX = toX; 54 this.toY = toY; 55 } 56 } 57 58 @Override 59 public void runPendingAnimations() { 60 boolean removalsPending = !mPendingRemovals.isEmpty(); 61 boolean movesPending = !mPendingMoves.isEmpty(); 62 boolean additionsPending = !mPendingAdditions.isEmpty(); 63 if (!removalsPending && !movesPending && !additionsPending) { 64 // nothing to animate 65 return; 66 } 67 // First, remove stuff 68 for (ViewHolder holder : mPendingRemovals) { 69 animateRemoveImpl(holder); 70 } 71 mPendingRemovals.clear(); 72 // Next, move stuff 73 if (movesPending) { 74 mMoves.addAll(mPendingMoves); 75 mPendingMoves.clear(); 76 Runnable mover = new Runnable() { 77 @Override 78 public void run() { 79 for (MoveInfo moveInfo : mMoves) { 80 animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, 81 moveInfo.toX, moveInfo.toY); 82 } 83 mMoves.clear(); 84 } 85 }; 86 if (removalsPending) { 87 View view = mMoves.get(0).holder.itemView; 88 ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration()); 89 } else { 90 mover.run(); 91 } 92 } 93 // Next, add stuff 94 if (additionsPending) { 95 mAdditions.addAll(mPendingAdditions); 96 mPendingAdditions.clear(); 97 Runnable adder = new Runnable() { 98 public void run() { 99 for (ViewHolder holder : mAdditions) { 100 animateAddImpl(holder); 101 } 102 mAdditions.clear(); 103 } 104 }; 105 if (removalsPending || movesPending) { 106 View view = mAdditions.get(0).itemView; 107 ViewCompat.postOnAnimationDelayed(view, adder, 108 (removalsPending ? getRemoveDuration() : 0) + 109 (movesPending ? getMoveDuration() : 0)); 110 } else { 111 adder.run(); 112 } 113 } 114 } 115 116 @Override 117 public boolean animateRemove(final ViewHolder holder) { 118 mPendingRemovals.add(holder); 119 return true; 120 } 121 122 private void animateRemoveImpl(final ViewHolder holder) { 123 final View view = holder.itemView; 124 ViewCompat.animate(view).cancel(); 125 ViewCompat.animate(view).setDuration(getRemoveDuration()). 126 alpha(0).setListener(new VpaListenerAdapter() { 127 @Override 128 public void onAnimationEnd(View view) { 129 ViewCompat.setAlpha(view, 1); 130 dispatchRemoveFinished(holder); 131 mRemoveAnimations.remove(holder); 132 dispatchFinishedWhenDone(); 133 } 134 }).start(); 135 mRemoveAnimations.add(holder); 136 } 137 138 @Override 139 public boolean animateAdd(final ViewHolder holder) { 140 ViewCompat.setAlpha(holder.itemView, 0); 141 mPendingAdditions.add(holder); 142 return true; 143 } 144 145 private void animateAddImpl(final ViewHolder holder) { 146 final View view = holder.itemView; 147 ViewCompat.animate(view).cancel(); 148 ViewCompat.animate(view).alpha(1).setDuration(getAddDuration()). 149 setListener(new VpaListenerAdapter() { 150 @Override 151 public void onAnimationCancel(View view) { 152 ViewCompat.setAlpha(view, 1); 153 } 154 155 @Override 156 public void onAnimationEnd(View view) { 157 dispatchAddFinished(holder); 158 mAddAnimations.remove(holder); 159 dispatchFinishedWhenDone(); 160 } 161 }).start(); 162 mAddAnimations.add(holder); 163 } 164 165 @Override 166 public boolean animateMove(final ViewHolder holder, int fromX, int fromY, 167 int toX, int toY) { 168 final View view = holder.itemView; 169 int deltaX = toX - fromX; 170 int deltaY = toY - fromY; 171 if (deltaX == 0 && deltaY == 0) { 172 dispatchMoveFinished(holder); 173 return false; 174 } 175 if (deltaX != 0) { 176 ViewCompat.setTranslationX(view, -deltaX); 177 } 178 if (deltaY != 0) { 179 ViewCompat.setTranslationY(view, -deltaY); 180 } 181 mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); 182 return true; 183 } 184 185 private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { 186 final View view = holder.itemView; 187 final int deltaX = toX - fromX; 188 final int deltaY = toY - fromY; 189 ViewCompat.animate(view).cancel(); 190 if (deltaX != 0) { 191 ViewCompat.animate(view).translationX(0); 192 } 193 if (deltaY != 0) { 194 ViewCompat.animate(view).translationY(0); 195 } 196 // TODO: make EndActions end listeners instead, since end actions aren't called when 197 // vpas are canceled (and can't end them. why?) 198 // need listener functionality in VPACompat for this. Ick. 199 ViewCompat.animate(view).setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { 200 @Override 201 public void onAnimationCancel(View view) { 202 if (deltaX != 0) { 203 ViewCompat.setTranslationX(view, 0); 204 } 205 if (deltaY != 0) { 206 ViewCompat.setTranslationY(view, 0); 207 } 208 } 209 @Override 210 public void onAnimationEnd(View view) { 211 dispatchMoveFinished(holder); 212 mMoveAnimations.remove(holder); 213 dispatchFinishedWhenDone(); 214 } 215 }).start(); 216 mMoveAnimations.add(holder); 217 } 218 219 @Override 220 public void endAnimation(ViewHolder item) { 221 final View view = item.itemView; 222 ViewCompat.animate(view).cancel(); 223 if (mPendingMoves.contains(item)) { 224 ViewCompat.setTranslationY(view, 0); 225 ViewCompat.setTranslationX(view, 0); 226 dispatchMoveFinished(item); 227 mPendingMoves.remove(item); 228 } 229 if (mPendingRemovals.contains(item)) { 230 dispatchRemoveFinished(item); 231 mPendingRemovals.remove(item); 232 } 233 if (mPendingAdditions.contains(item)) { 234 ViewCompat.setAlpha(view, 1); 235 dispatchAddFinished(item); 236 mPendingAdditions.remove(item); 237 } 238 if (mMoveAnimations.contains(item)) { 239 ViewCompat.setTranslationY(view, 0); 240 ViewCompat.setTranslationX(view, 0); 241 dispatchMoveFinished(item); 242 mMoveAnimations.remove(item); 243 } 244 if (mRemoveAnimations.contains(item)) { 245 ViewCompat.setAlpha(view, 1); 246 dispatchRemoveFinished(item); 247 mRemoveAnimations.remove(item); 248 } 249 if (mAddAnimations.contains(item)) { 250 ViewCompat.setAlpha(view, 1); 251 dispatchAddFinished(item); 252 mAddAnimations.remove(item); 253 } 254 dispatchFinishedWhenDone(); 255 } 256 257 @Override 258 public boolean isRunning() { 259 return (!mMoveAnimations.isEmpty() || 260 !mRemoveAnimations.isEmpty() || 261 !mAddAnimations.isEmpty() || 262 !mMoves.isEmpty() || 263 !mAdditions.isEmpty()); 264 } 265 266 /** 267 * Check the state of currently pending and running animations. If there are none 268 * pending/running, call {@link #dispatchAnimationsFinished()} to notify any 269 * listeners. 270 */ 271 private void dispatchFinishedWhenDone() { 272 if (!isRunning()) { 273 dispatchAnimationsFinished(); 274 } 275 } 276 277 @Override 278 public void endAnimations() { 279 int count = mPendingMoves.size(); 280 for (int i = count - 1; i >= 0; i--) { 281 MoveInfo item = mPendingMoves.get(i); 282 View view = item.holder.itemView; 283 ViewCompat.animate(view).cancel(); 284 ViewCompat.setTranslationY(view, 0); 285 ViewCompat.setTranslationX(view, 0); 286 dispatchMoveFinished(item.holder); 287 mPendingMoves.remove(item); 288 } 289 count = mPendingRemovals.size(); 290 for (int i = count - 1; i >= 0; i--) { 291 ViewHolder item = mPendingRemovals.get(i); 292 dispatchRemoveFinished(item); 293 mPendingRemovals.remove(item); 294 } 295 count = mPendingAdditions.size(); 296 for (int i = count - 1; i >= 0; i--) { 297 ViewHolder item = mPendingAdditions.get(i); 298 View view = item.itemView; 299 ViewCompat.setAlpha(view, 1); 300 dispatchAddFinished(item); 301 mPendingAdditions.remove(item); 302 } 303 if (!isRunning()) { 304 return; 305 } 306 count = mMoveAnimations.size(); 307 for (int i = count - 1; i >= 0; i--) { 308 ViewHolder item = mMoveAnimations.get(i); 309 View view = item.itemView; 310 ViewCompat.animate(view).cancel(); 311 ViewCompat.setTranslationY(view, 0); 312 ViewCompat.setTranslationX(view, 0); 313 dispatchMoveFinished(item); 314 mMoveAnimations.remove(item); 315 } 316 count = mRemoveAnimations.size(); 317 for (int i = count - 1; i >= 0; i--) { 318 ViewHolder item = mRemoveAnimations.get(i); 319 View view = item.itemView; 320 ViewCompat.animate(view).cancel(); 321 ViewCompat.setAlpha(view, 1); 322 dispatchRemoveFinished(item); 323 mRemoveAnimations.remove(item); 324 } 325 count = mAddAnimations.size(); 326 for (int i = count - 1; i >= 0; i--) { 327 ViewHolder item = mAddAnimations.get(i); 328 View view = item.itemView; 329 ViewCompat.animate(view).cancel(); 330 ViewCompat.setAlpha(view, 1); 331 dispatchAddFinished(item); 332 mAddAnimations.remove(item); 333 } 334 mMoves.clear(); 335 mAdditions.clear(); 336 dispatchAnimationsFinished(); 337 } 338 339 private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { 340 @Override 341 public void onAnimationStart(View view) {} 342 343 @Override 344 public void onAnimationEnd(View view) {} 345 346 @Override 347 public void onAnimationCancel(View view) {} 348 }; 349} 350