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