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 }