1 /* 2 * Copyright (C) 2016 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.deskclock; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.support.annotation.NonNull; 25 import android.support.v4.util.ArrayMap; 26 import android.support.v7.widget.RecyclerView.State; 27 import android.support.v7.widget.RecyclerView.ViewHolder; 28 import android.support.v7.widget.SimpleItemAnimator; 29 import android.view.View; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Map; 34 35 import static android.view.View.TRANSLATION_Y; 36 import static android.view.View.TRANSLATION_X; 37 38 public class ItemAnimator extends SimpleItemAnimator { 39 40 private final List<Animator> mAddAnimatorsList = new ArrayList<>(); 41 private final List<Animator> mRemoveAnimatorsList = new ArrayList<>(); 42 private final List<Animator> mChangeAnimatorsList = new ArrayList<>(); 43 private final List<Animator> mMoveAnimatorsList = new ArrayList<>(); 44 45 private final Map<ViewHolder, Animator> mAnimators = new ArrayMap<>(); 46 47 @Override animateRemove(final ViewHolder holder)48 public boolean animateRemove(final ViewHolder holder) { 49 endAnimation(holder); 50 51 final float prevAlpha = holder.itemView.getAlpha(); 52 53 final Animator removeAnimator = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 0f); 54 removeAnimator.setDuration(getRemoveDuration()); 55 removeAnimator.addListener(new AnimatorListenerAdapter() { 56 @Override 57 public void onAnimationStart(Animator animator) { 58 dispatchRemoveStarting(holder); 59 } 60 61 @Override 62 public void onAnimationEnd(Animator animator) { 63 animator.removeAllListeners(); 64 mAnimators.remove(holder); 65 holder.itemView.setAlpha(prevAlpha); 66 dispatchRemoveFinished(holder); 67 } 68 }); 69 mRemoveAnimatorsList.add(removeAnimator); 70 mAnimators.put(holder, removeAnimator); 71 return true; 72 } 73 74 @Override animateAdd(final ViewHolder holder)75 public boolean animateAdd(final ViewHolder holder) { 76 endAnimation(holder); 77 78 final float prevAlpha = holder.itemView.getAlpha(); 79 holder.itemView.setAlpha(0f); 80 81 final Animator addAnimator = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 1f) 82 .setDuration(getAddDuration()); 83 addAnimator.addListener(new AnimatorListenerAdapter() { 84 @Override 85 public void onAnimationStart(Animator animator) { 86 dispatchAddStarting(holder); 87 } 88 89 @Override 90 public void onAnimationEnd(Animator animator) { 91 animator.removeAllListeners(); 92 mAnimators.remove(holder); 93 holder.itemView.setAlpha(prevAlpha); 94 dispatchAddFinished(holder); 95 } 96 }); 97 mAddAnimatorsList.add(addAnimator); 98 mAnimators.put(holder, addAnimator); 99 return true; 100 } 101 102 @Override animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY)103 public boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { 104 endAnimation(holder); 105 106 final int deltaX = toX - fromX; 107 final int deltaY = toY - fromY; 108 final long moveDuration = getMoveDuration(); 109 110 if (deltaX == 0 && deltaY == 0) { 111 dispatchMoveFinished(holder); 112 return false; 113 } 114 115 final View view = holder.itemView; 116 final float prevTranslationX = view.getTranslationX(); 117 final float prevTranslationY = view.getTranslationY(); 118 view.setTranslationX(-deltaX); 119 view.setTranslationY(-deltaY); 120 121 final ObjectAnimator moveAnimator; 122 if (deltaX != 0 && deltaY != 0) { 123 final PropertyValuesHolder moveX = PropertyValuesHolder.ofFloat(TRANSLATION_X, 0f); 124 final PropertyValuesHolder moveY = PropertyValuesHolder.ofFloat(TRANSLATION_Y, 0f); 125 moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX, moveY); 126 } else if (deltaX != 0) { 127 final PropertyValuesHolder moveX = PropertyValuesHolder.ofFloat(TRANSLATION_X, 0f); 128 moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveX); 129 } else { 130 final PropertyValuesHolder moveY = PropertyValuesHolder.ofFloat(TRANSLATION_Y, 0f); 131 moveAnimator = ObjectAnimator.ofPropertyValuesHolder(holder.itemView, moveY); 132 } 133 134 moveAnimator.setDuration(moveDuration); 135 moveAnimator.setInterpolator(AnimatorUtils.INTERPOLATOR_FAST_OUT_SLOW_IN); 136 moveAnimator.addListener(new AnimatorListenerAdapter() { 137 @Override 138 public void onAnimationStart(Animator animator) { 139 dispatchMoveStarting(holder); 140 } 141 142 @Override 143 public void onAnimationEnd(Animator animator) { 144 animator.removeAllListeners(); 145 mAnimators.remove(holder); 146 view.setTranslationX(prevTranslationX); 147 view.setTranslationY(prevTranslationY); 148 dispatchMoveFinished(holder); 149 } 150 }); 151 mMoveAnimatorsList.add(moveAnimator); 152 mAnimators.put(holder, moveAnimator); 153 154 return true; 155 } 156 157 @Override animateChange(@onNull final ViewHolder oldHolder, @NonNull final ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo)158 public boolean animateChange(@NonNull final ViewHolder oldHolder, 159 @NonNull final ViewHolder newHolder, @NonNull ItemHolderInfo preInfo, 160 @NonNull ItemHolderInfo postInfo) { 161 endAnimation(oldHolder); 162 endAnimation(newHolder); 163 164 final long changeDuration = getChangeDuration(); 165 List<Object> payloads = preInfo instanceof PayloadItemHolderInfo 166 ? ((PayloadItemHolderInfo) preInfo).getPayloads() : null; 167 168 if (oldHolder == newHolder) { 169 final Animator animator = ((OnAnimateChangeListener) newHolder) 170 .onAnimateChange(payloads, preInfo.left, preInfo.top, preInfo.right, 171 preInfo.bottom, changeDuration); 172 if (animator == null) { 173 dispatchChangeFinished(newHolder, false); 174 return false; 175 } 176 animator.addListener(new AnimatorListenerAdapter() { 177 @Override 178 public void onAnimationStart(Animator animator) { 179 dispatchChangeStarting(newHolder, false); 180 } 181 182 @Override 183 public void onAnimationEnd(Animator animator) { 184 animator.removeAllListeners(); 185 mAnimators.remove(newHolder); 186 dispatchChangeFinished(newHolder, false); 187 } 188 }); 189 mChangeAnimatorsList.add(animator); 190 mAnimators.put(newHolder, animator); 191 return true; 192 } else if (!(oldHolder instanceof OnAnimateChangeListener) || 193 !(newHolder instanceof OnAnimateChangeListener)) { 194 // Both holders must implement OnAnimateChangeListener in order to animate. 195 dispatchChangeFinished(oldHolder, true); 196 dispatchChangeFinished(newHolder, true); 197 return false; 198 } 199 200 final Animator oldChangeAnimator = ((OnAnimateChangeListener) oldHolder) 201 .onAnimateChange(oldHolder, newHolder, changeDuration); 202 if (oldChangeAnimator != null) { 203 oldChangeAnimator.addListener(new AnimatorListenerAdapter() { 204 @Override 205 public void onAnimationStart(Animator animator) { 206 dispatchChangeStarting(oldHolder, true); 207 } 208 209 @Override 210 public void onAnimationEnd(Animator animator) { 211 animator.removeAllListeners(); 212 mAnimators.remove(oldHolder); 213 dispatchChangeFinished(oldHolder, true); 214 } 215 }); 216 mAnimators.put(oldHolder, oldChangeAnimator); 217 mChangeAnimatorsList.add(oldChangeAnimator); 218 } else { 219 dispatchChangeFinished(oldHolder, true); 220 } 221 222 final Animator newChangeAnimator = ((OnAnimateChangeListener) newHolder) 223 .onAnimateChange(oldHolder, newHolder, changeDuration); 224 if (newChangeAnimator != null) { 225 newChangeAnimator.addListener(new AnimatorListenerAdapter() { 226 @Override 227 public void onAnimationStart(Animator animator) { 228 dispatchChangeStarting(newHolder, false); 229 } 230 231 @Override 232 public void onAnimationEnd(Animator animator) { 233 animator.removeAllListeners(); 234 mAnimators.remove(newHolder); 235 dispatchChangeFinished(newHolder, false); 236 } 237 }); 238 mAnimators.put(newHolder, newChangeAnimator); 239 mChangeAnimatorsList.add(newChangeAnimator); 240 } else { 241 dispatchChangeFinished(newHolder, false); 242 } 243 244 return true; 245 } 246 247 @Override animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)248 public boolean animateChange(ViewHolder oldHolder, 249 ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { 250 /* Unused */ 251 throw new IllegalStateException("This method should not be used"); 252 } 253 254 @Override runPendingAnimations()255 public void runPendingAnimations() { 256 final AnimatorSet removeAnimatorSet = new AnimatorSet(); 257 removeAnimatorSet.playTogether(mRemoveAnimatorsList); 258 mRemoveAnimatorsList.clear(); 259 260 final AnimatorSet addAnimatorSet = new AnimatorSet(); 261 addAnimatorSet.playTogether(mAddAnimatorsList); 262 mAddAnimatorsList.clear(); 263 264 final AnimatorSet changeAnimatorSet = new AnimatorSet(); 265 changeAnimatorSet.playTogether(mChangeAnimatorsList); 266 mChangeAnimatorsList.clear(); 267 268 final AnimatorSet moveAnimatorSet = new AnimatorSet(); 269 moveAnimatorSet.playTogether(mMoveAnimatorsList); 270 mMoveAnimatorsList.clear(); 271 272 final AnimatorSet pendingAnimatorSet = new AnimatorSet(); 273 pendingAnimatorSet.addListener(new AnimatorListenerAdapter() { 274 @Override 275 public void onAnimationEnd(Animator animator) { 276 animator.removeAllListeners(); 277 dispatchFinishedWhenDone(); 278 } 279 }); 280 // Required order: removes, then changes & moves simultaneously, then additions. There are 281 // redundant edges because changes or moves may be empty, causing the removes to incorrectly 282 // play immediately. 283 pendingAnimatorSet.play(removeAnimatorSet).before(changeAnimatorSet); 284 pendingAnimatorSet.play(removeAnimatorSet).before(moveAnimatorSet); 285 pendingAnimatorSet.play(changeAnimatorSet).with(moveAnimatorSet); 286 pendingAnimatorSet.play(addAnimatorSet).after(changeAnimatorSet); 287 pendingAnimatorSet.play(addAnimatorSet).after(moveAnimatorSet); 288 pendingAnimatorSet.start(); 289 } 290 291 @Override endAnimation(ViewHolder holder)292 public void endAnimation(ViewHolder holder) { 293 final Animator animator = mAnimators.get(holder); 294 295 mAnimators.remove(holder); 296 mAddAnimatorsList.remove(animator); 297 mRemoveAnimatorsList.remove(animator); 298 mChangeAnimatorsList.remove(animator); 299 mMoveAnimatorsList.remove(animator); 300 301 if (animator != null) { 302 animator.end(); 303 } 304 305 dispatchFinishedWhenDone(); 306 } 307 308 @Override endAnimations()309 public void endAnimations() { 310 final List<Animator> animatorList = new ArrayList<>(mAnimators.values()); 311 for (Animator animator : animatorList) { 312 animator.end(); 313 } 314 dispatchFinishedWhenDone(); 315 } 316 317 @Override isRunning()318 public boolean isRunning() { 319 return !mAnimators.isEmpty(); 320 } 321 dispatchFinishedWhenDone()322 private void dispatchFinishedWhenDone() { 323 if (!isRunning()) { 324 dispatchAnimationsFinished(); 325 } 326 } 327 328 @Override recordPreLayoutInformation(@onNull State state, @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, @NonNull List<Object> payloads)329 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, 330 @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, 331 @NonNull List<Object> payloads) { 332 final ItemHolderInfo itemHolderInfo = super.recordPreLayoutInformation(state, viewHolder, 333 changeFlags, payloads); 334 if (itemHolderInfo instanceof PayloadItemHolderInfo) { 335 ((PayloadItemHolderInfo) itemHolderInfo).setPayloads(payloads); 336 } 337 return itemHolderInfo; 338 } 339 340 @Override obtainHolderInfo()341 public ItemHolderInfo obtainHolderInfo() { 342 return new PayloadItemHolderInfo(); 343 } 344 345 @Override canReuseUpdatedViewHolder(@onNull ViewHolder viewHolder, @NonNull List<Object> payloads)346 public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, 347 @NonNull List<Object> payloads) { 348 final boolean defaultReusePolicy = super.canReuseUpdatedViewHolder(viewHolder, payloads); 349 // Whenever we have a payload, this is an in-place animation. 350 return !payloads.isEmpty() || defaultReusePolicy; 351 } 352 353 private static final class PayloadItemHolderInfo extends ItemHolderInfo { 354 private final List<Object> mPayloads = new ArrayList<>(); 355 setPayloads(List<Object> payloads)356 void setPayloads(List<Object> payloads) { 357 mPayloads.clear(); 358 mPayloads.addAll(payloads); 359 } 360 getPayloads()361 List<Object> getPayloads() { 362 return mPayloads; 363 } 364 } 365 366 public interface OnAnimateChangeListener { onAnimateChange(ViewHolder oldHolder, ViewHolder newHolder, long duration)367 Animator onAnimateChange(ViewHolder oldHolder, ViewHolder newHolder, long duration); onAnimateChange(List<Object> payloads, int fromLeft, int fromTop, int fromRight, int fromBottom, long duration)368 Animator onAnimateChange(List<Object> payloads, int fromLeft, int fromTop, int fromRight, 369 int fromBottom, long duration); 370 } 371 }