1 /*
2  * Copyright (C) 2019 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 package com.android.quickstep;
17 
18 import static android.view.View.ALPHA;
19 
20 import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
21 import static com.android.quickstep.views.TaskItemView.CONTENT_TRANSITION_PROGRESS;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.ObjectAnimator;
26 import android.view.View;
27 
28 import androidx.annotation.NonNull;
29 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
30 import androidx.recyclerview.widget.SimpleItemAnimator;
31 
32 import com.android.quickstep.views.TaskItemView;
33 
34 import java.util.ArrayList;
35 import java.util.Comparator;
36 import java.util.List;
37 
38 /**
39  * An item animator that is only set and used for the transition from the empty loading UI to
40  * the filled task content UI. The animation starts from the bottom to top, changing all valid
41  * empty item views to be filled and removing all extra empty views.
42  */
43 public final class ContentFillItemAnimator extends SimpleItemAnimator {
44 
45     private static final class PendingAnimation {
46         ViewHolder viewHolder;
47         int animType;
48 
PendingAnimation(ViewHolder vh, int type)49         PendingAnimation(ViewHolder vh, int type) {
50             viewHolder = vh;
51             animType = type;
52         }
53     }
54 
55     private static final int ANIM_TYPE_REMOVE = 0;
56     private static final int ANIM_TYPE_CHANGE = 1;
57 
58     private static final int ITEM_BETWEEN_DELAY = 40;
59     private static final int ITEM_CHANGE_DURATION = 150;
60     private static final int ITEM_REMOVE_DURATION = 150;
61 
62     /**
63      * Animations that have been registered to occur together at the next call of
64      * {@link #runPendingAnimations()} but have not started.
65      */
66     private final ArrayList<PendingAnimation> mPendingAnims = new ArrayList<>();
67 
68     /**
69      * Animations that have started and are running.
70      */
71     private final ArrayList<ObjectAnimator> mRunningAnims = new ArrayList<>();
72 
73     private Runnable mOnFinishRunnable;
74 
75     /**
76      * Set runnable to run after the content fill animation is fully completed.
77      *
78      * @param runnable runnable to run on end
79      */
setOnAnimationFinishedRunnable(Runnable runnable)80     public void setOnAnimationFinishedRunnable(Runnable runnable) {
81         mOnFinishRunnable = runnable;
82     }
83 
84     @Override
setChangeDuration(long changeDuration)85     public void setChangeDuration(long changeDuration) {
86         throw new UnsupportedOperationException("Cascading item animator cannot have animation "
87                 + "duration changed.");
88     }
89 
90     @Override
setRemoveDuration(long removeDuration)91     public void setRemoveDuration(long removeDuration) {
92         throw new UnsupportedOperationException("Cascading item animator cannot have animation "
93                 + "duration changed.");
94     }
95 
96     @Override
animateRemove(ViewHolder holder)97     public boolean animateRemove(ViewHolder holder) {
98         PendingAnimation pendAnim = new PendingAnimation(holder, ANIM_TYPE_REMOVE);
99         mPendingAnims.add(pendAnim);
100         return true;
101     }
102 
animateRemoveImpl(ViewHolder holder, long startDelay)103     private void animateRemoveImpl(ViewHolder holder, long startDelay) {
104         final View view = holder.itemView;
105         if (holder.itemView.getAlpha() == 0) {
106             // View is already visually removed. We can just get rid of it now.
107             view.setAlpha(1.0f);
108             dispatchRemoveFinished(holder);
109             dispatchFinishedWhenDone();
110             return;
111         }
112         final ObjectAnimator anim = ObjectAnimator.ofFloat(
113                 holder.itemView, ALPHA, holder.itemView.getAlpha(), 0.0f);
114         anim.setDuration(ITEM_REMOVE_DURATION).setStartDelay(startDelay);
115         anim.addListener(
116                 new AnimatorListenerAdapter() {
117                     @Override
118                     public void onAnimationStart(Animator animation) {
119                         dispatchRemoveStarting(holder);
120                     }
121 
122                     @Override
123                     public void onAnimationEnd(Animator animation) {
124                         view.setAlpha(1);
125                         dispatchRemoveFinished(holder);
126                         mRunningAnims.remove(anim);
127                         dispatchFinishedWhenDone();
128                     }
129                 }
130         );
131         anim.start();
132         mRunningAnims.add(anim);
133     }
134 
135     @Override
animateAdd(ViewHolder holder)136     public boolean animateAdd(ViewHolder holder) {
137         dispatchAddFinished(holder);
138         return false;
139     }
140 
141     @Override
animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY)142     public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX,
143             int toY) {
144         dispatchMoveFinished(holder);
145         return false;
146     }
147 
148     @Override
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop)149     public boolean animateChange(ViewHolder oldHolder,
150             ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
151         // Only support changes where the holders are the same
152         if (oldHolder == newHolder) {
153             PendingAnimation pendAnim = new PendingAnimation(oldHolder, ANIM_TYPE_CHANGE);
154             mPendingAnims.add(pendAnim);
155             return true;
156         }
157         dispatchChangeFinished(oldHolder, true /* oldItem */);
158         dispatchChangeFinished(newHolder, false /* oldItem */);
159         return false;
160     }
161 
animateChangeImpl(ViewHolder viewHolder, long startDelay)162     private void animateChangeImpl(ViewHolder viewHolder, long startDelay) {
163         TaskItemView itemView = (TaskItemView) viewHolder.itemView;
164         if (itemView.getAlpha() == 0) {
165             // View is still not visible, so we can finish the change immediately.
166             CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
167             dispatchChangeFinished(viewHolder, true /* oldItem */);
168             dispatchFinishedWhenDone();
169             return;
170         }
171         final ObjectAnimator anim =
172                 ObjectAnimator.ofFloat(itemView, CONTENT_TRANSITION_PROGRESS, 0.0f, 1.0f);
173         anim.setDuration(ITEM_CHANGE_DURATION).setStartDelay(startDelay);
174         anim.addListener(
175                 new AnimatorListenerAdapter() {
176                     @Override
177                     public void onAnimationStart(Animator animation) {
178                         dispatchChangeStarting(viewHolder, true /* oldItem */);
179                     }
180 
181                     @Override
182                     public void onAnimationEnd(Animator animation) {
183                         CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
184                         dispatchChangeFinished(viewHolder, true /* oldItem */);
185                         mRunningAnims.remove(anim);
186                         dispatchFinishedWhenDone();
187                     }
188                 }
189         );
190         anim.start();
191         mRunningAnims.add(anim);
192     }
193 
194     @Override
runPendingAnimations()195     public void runPendingAnimations() {
196         // Run animations bottom to top.
197         mPendingAnims.sort(Comparator.comparingInt(o -> -o.viewHolder.itemView.getBottom()));
198         int delay = 0;
199         while (!mPendingAnims.isEmpty()) {
200             PendingAnimation curAnim = mPendingAnims.remove(0);
201             ViewHolder vh = curAnim.viewHolder;
202             switch (curAnim.animType) {
203                 case ANIM_TYPE_REMOVE:
204                     animateRemoveImpl(vh, delay);
205                     break;
206                 case ANIM_TYPE_CHANGE:
207                     animateChangeImpl(vh, delay);
208                     break;
209                 default:
210                     break;
211             }
212             delay += ITEM_BETWEEN_DELAY;
213         }
214     }
215 
216     @Override
endAnimation(@onNull ViewHolder item)217     public void endAnimation(@NonNull ViewHolder item) {
218         for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
219             endPendingAnimation(mPendingAnims.get(i));
220             mPendingAnims.remove(i);
221         }
222         dispatchFinishedWhenDone();
223     }
224 
225     @Override
endAnimations()226     public void endAnimations() {
227         if (!isRunning()) {
228             return;
229         }
230         for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
231             endPendingAnimation(mPendingAnims.get(i));
232             mPendingAnims.remove(i);
233         }
234         for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
235             ObjectAnimator anim = mRunningAnims.get(i);
236             // This calls the on end animation callback which will set values to their end target.
237             anim.cancel();
238         }
239         dispatchFinishedWhenDone();
240     }
241 
endPendingAnimation(PendingAnimation pendAnim)242     private void endPendingAnimation(PendingAnimation pendAnim) {
243         ViewHolder item = pendAnim.viewHolder;
244         switch (pendAnim.animType) {
245             case ANIM_TYPE_REMOVE:
246                 item.itemView.setAlpha(1.0f);
247                 dispatchRemoveFinished(item);
248                 break;
249             case ANIM_TYPE_CHANGE:
250                 CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
251                 dispatchChangeFinished(item, true /* oldItem */);
252                 break;
253             default:
254                 break;
255         }
256     }
257 
258     @Override
isRunning()259     public boolean isRunning() {
260         return !mPendingAnims.isEmpty() || !mRunningAnims.isEmpty();
261     }
262 
263     @Override
canReuseUpdatedViewHolder(@onNull ViewHolder viewHolder, @NonNull List<Object> payloads)264     public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
265             @NonNull List<Object> payloads) {
266         if (!payloads.isEmpty()
267                 && (int) payloads.get(0) == CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT) {
268             return true;
269         }
270         return super.canReuseUpdatedViewHolder(viewHolder, payloads);
271     }
272 
dispatchFinishedWhenDone()273     private void dispatchFinishedWhenDone() {
274         if (!isRunning()) {
275             dispatchAnimationsFinished();
276             if (mOnFinishRunnable != null) {
277                 mOnFinishRunnable.run();
278             }
279         }
280     }
281 }
282