TaskStackAnimationHelper.java revision c0d7058b14c24cd07912f5629c26b39b7b4673d5
1/* 2 * Copyright (C) 2015 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 17package com.android.systemui.recents.views; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.RectF; 24import android.view.View; 25 26import com.android.systemui.Interpolators; 27import com.android.systemui.R; 28import com.android.systemui.recents.Recents; 29import com.android.systemui.recents.RecentsActivityLaunchState; 30import com.android.systemui.recents.RecentsConfiguration; 31import com.android.systemui.recents.misc.ReferenceCountedTrigger; 32import com.android.systemui.recents.model.Task; 33import com.android.systemui.recents.model.TaskStack; 34 35import java.util.List; 36 37/** 38 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView}, 39 * but not the contents of the {@link TaskView}s. 40 */ 41public class TaskStackAnimationHelper { 42 43 /** 44 * Callbacks from the helper to coordinate view-content animations with view animations. 45 */ 46 public interface Callbacks { 47 /** 48 * Callback to prepare for the start animation for the launch target {@link TaskView}. 49 */ 50 void onPrepareLaunchTargetForEnterAnimation(); 51 52 /** 53 * Callback to start the animation for the launch target {@link TaskView}. 54 */ 55 void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled, 56 ReferenceCountedTrigger postAnimationTrigger); 57 58 /** 59 * Callback to start the animation for the launch target {@link TaskView} when it is 60 * launched from Recents. 61 */ 62 void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, 63 ReferenceCountedTrigger postAnimationTrigger); 64 } 65 66 private TaskStackView mStackView; 67 68 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 69 70 public TaskStackAnimationHelper(Context context, TaskStackView stackView) { 71 mStackView = stackView; 72 } 73 74 /** 75 * Prepares the stack views and puts them in their initial animation state while visible, before 76 * the in-app enter animations start (after the window-transition completes). 77 */ 78 public void prepareForEnterAnimation() { 79 RecentsConfiguration config = Recents.getConfiguration(); 80 RecentsActivityLaunchState launchState = config.getLaunchState(); 81 Resources res = mStackView.getResources(); 82 83 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 84 TaskStackViewScroller stackScroller = mStackView.getScroller(); 85 TaskStack stack = mStackView.getStack(); 86 Task launchTargetTask = stack.getLaunchTarget(); 87 88 // Break early if there are no tasks 89 if (stack.getTaskCount() == 0) { 90 return; 91 } 92 93 int offscreenY = stackLayout.mStackRect.bottom; 94 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 95 R.dimen.recents_task_view_affiliate_group_enter_offset); 96 97 // Prepare each of the task views for their enter animation from front to back 98 List<TaskView> taskViews = mStackView.getTaskViews(); 99 for (int i = taskViews.size() - 1; i >= 0; i--) { 100 TaskView tv = taskViews.get(i); 101 Task task = tv.getTask(); 102 boolean currentTaskOccludesLaunchTarget = (launchTargetTask != null && 103 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask)); 104 boolean hideTask = (launchTargetTask != null && 105 launchTargetTask.isFreeformTask() && task.isFreeformTask()); 106 107 // Get the current transform for the task, which will be used to position it offscreen 108 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 109 null); 110 111 if (hideTask) { 112 tv.setVisibility(View.INVISIBLE); 113 } else if (launchState.launchedHasConfigurationChanged) { 114 // Just load the views as-is 115 } else if (launchState.launchedFromAppWithThumbnail) { 116 if (task.isLaunchTarget) { 117 tv.onPrepareLaunchTargetForEnterAnimation(); 118 } else if (currentTaskOccludesLaunchTarget) { 119 // Move the task view slightly lower so we can animate it in 120 RectF bounds = new RectF(mTmpTransform.rect); 121 bounds.offset(0, taskViewAffiliateGroupEnterOffset); 122 tv.setClipViewInStack(false); 123 tv.setAlpha(0f); 124 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, 125 (int) bounds.right, (int) bounds.bottom); 126 } 127 } else if (launchState.launchedFromHome) { 128 // Move the task view off screen (below) so we can animate it in 129 RectF bounds = new RectF(mTmpTransform.rect); 130 bounds.offsetTo(bounds.left, offscreenY); 131 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, 132 (int) bounds.bottom); 133 } 134 } 135 } 136 137 /** 138 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places 139 * depending on how Recents was triggered. 140 */ 141 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { 142 RecentsConfiguration config = Recents.getConfiguration(); 143 RecentsActivityLaunchState launchState = config.getLaunchState(); 144 Resources res = mStackView.getResources(); 145 146 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 147 TaskStackViewScroller stackScroller = mStackView.getScroller(); 148 TaskStack stack = mStackView.getStack(); 149 Task launchTargetTask = stack.getLaunchTarget(); 150 151 // Break early if there are no tasks 152 if (stack.getTaskCount() == 0) { 153 return; 154 } 155 156 int taskViewEnterFromAppDuration = res.getInteger( 157 R.integer.recents_task_enter_from_app_duration); 158 int taskViewEnterFromAffiliatedAppDuration = res.getInteger( 159 R.integer.recents_task_enter_from_affiliated_app_duration); 160 int taskViewEnterFromHomeDuration = res.getInteger( 161 R.integer.recents_task_enter_from_home_duration); 162 int taskViewEnterFromHomeStaggerDelay = res.getInteger( 163 R.integer.recents_task_enter_from_home_stagger_delay); 164 165 // Create enter animations for each of the views from front to back 166 List<TaskView> taskViews = mStackView.getTaskViews(); 167 int taskViewCount = taskViews.size(); 168 for (int i = taskViewCount - 1; i >= 0; i--) { 169 final TaskView tv = taskViews.get(i); 170 Task task = tv.getTask(); 171 boolean currentTaskOccludesLaunchTarget = false; 172 if (launchTargetTask != null) { 173 currentTaskOccludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, 174 launchTargetTask); 175 } 176 177 // Get the current transform for the task, which will be updated to the final transform 178 // to animate to depending on how recents was invoked 179 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 180 null); 181 182 if (launchState.launchedFromAppWithThumbnail) { 183 if (task.isLaunchTarget) { 184 tv.onStartLaunchTargetEnterAnimation(taskViewEnterFromAppDuration, 185 mStackView.mScreenPinningEnabled, postAnimationTrigger); 186 } else { 187 // Animate the task up if it was occluding the launch target 188 if (currentTaskOccludesLaunchTarget) { 189 TaskViewAnimation taskAnimation = new TaskViewAnimation( 190 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, 191 new AnimatorListenerAdapter() { 192 @Override 193 public void onAnimationEnd(Animator animation) { 194 postAnimationTrigger.decrement(); 195 tv.setClipViewInStack(false); 196 } 197 }); 198 postAnimationTrigger.increment(); 199 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 200 } 201 } 202 203 } else if (launchState.launchedFromHome) { 204 // Animate the tasks up 205 int frontIndex = (taskViewCount - i - 1); 206 int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; 207 int duration = taskViewEnterFromHomeDuration + 208 frontIndex * taskViewEnterFromHomeStaggerDelay; 209 210 TaskViewAnimation taskAnimation = new TaskViewAnimation(delay, 211 duration, Interpolators.DECELERATE_QUINT, 212 postAnimationTrigger.decrementOnAnimationEnd()); 213 postAnimationTrigger.increment(); 214 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 215 } 216 } 217 } 218 219 /** 220 * Starts an in-app animation to hide all the task views so that we can transition back home. 221 */ 222 public void startExitToHomeAnimation(boolean animated, 223 ReferenceCountedTrigger postAnimationTrigger) { 224 Resources res = mStackView.getResources(); 225 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 226 TaskStackViewScroller stackScroller = mStackView.getScroller(); 227 TaskStack stack = mStackView.getStack(); 228 229 // Break early if there are no tasks 230 if (stack.getTaskCount() == 0) { 231 return; 232 } 233 234 int offscreenY = stackLayout.mStackRect.bottom; 235 int taskViewExitToHomeDuration = res.getInteger( 236 R.integer.recents_task_exit_to_home_duration); 237 238 // Create the animations for each of the tasks 239 List<TaskView> taskViews = mStackView.getTaskViews(); 240 int taskViewCount = taskViews.size(); 241 for (int i = 0; i < taskViewCount; i++) { 242 TaskView tv = taskViews.get(i); 243 Task task = tv.getTask(); 244 TaskViewAnimation taskAnimation = new TaskViewAnimation( 245 animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN, 246 postAnimationTrigger.decrementOnAnimationEnd()); 247 postAnimationTrigger.increment(); 248 249 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 250 null); 251 mTmpTransform.rect.offsetTo(mTmpTransform.rect.left, offscreenY); 252 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 253 } 254 } 255 256 /** 257 * Starts the animation for the launching task view, hiding any tasks that might occlude the 258 * window transition for the launching task. 259 */ 260 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, 261 final ReferenceCountedTrigger postAnimationTrigger) { 262 Resources res = mStackView.getResources(); 263 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 264 TaskStackViewScroller stackScroller = mStackView.getScroller(); 265 266 int taskViewExitToAppDuration = res.getInteger( 267 R.integer.recents_task_exit_to_app_duration); 268 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 269 R.dimen.recents_task_view_affiliate_group_enter_offset); 270 271 Task launchingTask = launchingTaskView.getTask(); 272 List<TaskView> taskViews = mStackView.getTaskViews(); 273 int taskViewCount = taskViews.size(); 274 for (int i = 0; i < taskViewCount; i++) { 275 TaskView tv = taskViews.get(i); 276 Task task = tv.getTask(); 277 boolean currentTaskOccludesLaunchTarget = (launchingTask != null && 278 launchingTask.group.isTaskAboveTask(task, launchingTask)); 279 280 if (tv == launchingTaskView) { 281 tv.setClipViewInStack(false); 282 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, 283 screenPinningRequested, postAnimationTrigger); 284 } else if (currentTaskOccludesLaunchTarget) { 285 // Animate this task out of view 286 TaskViewAnimation taskAnimation = new TaskViewAnimation( 287 taskViewExitToAppDuration, Interpolators.ALPHA_OUT, 288 postAnimationTrigger.decrementOnAnimationEnd()); 289 postAnimationTrigger.increment(); 290 291 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 292 null); 293 mTmpTransform.alpha = 0f; 294 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 295 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 296 } 297 } 298 } 299 300 /** 301 * Starts the delete animation for the specified {@link TaskView}. 302 */ 303 public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView, 304 final ReferenceCountedTrigger postAnimationTrigger) { 305 Resources res = mStackView.getResources(); 306 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 307 TaskStackViewScroller stackScroller = mStackView.getScroller(); 308 309 int taskViewRemoveAnimDuration = res.getInteger( 310 R.integer.recents_animate_task_view_remove_duration); 311 int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize( 312 R.dimen.recents_task_view_remove_anim_translation_x); 313 314 // Disabling clipping with the stack while the view is animating away 315 deleteTaskView.setClipViewInStack(false); 316 317 // Compose the new animation and transform and star the animation 318 TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration, 319 Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { 320 @Override 321 public void onAnimationEnd(Animator animation) { 322 postAnimationTrigger.decrement(); 323 324 // Re-enable clipping with the stack (we will reuse this view) 325 deleteTaskView.setClipViewInStack(true); 326 } 327 }); 328 postAnimationTrigger.increment(); 329 330 stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform, 331 null); 332 mTmpTransform.alpha = 0f; 333 mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0); 334 mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); 335 } 336 337 /** 338 * Starts the animation to hide the {@link TaskView}s when the history is shown. 339 */ 340 public void startShowHistoryAnimation(ReferenceCountedTrigger postAnimationTrigger) { 341 Resources res = mStackView.getResources(); 342 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 343 TaskStackViewScroller stackScroller = mStackView.getScroller(); 344 345 int offscreenY = stackLayout.mStackRect.bottom; 346 int historyTransitionDuration = res.getInteger( 347 R.integer.recents_history_transition_duration); 348 int startDelayIncr = 16; 349 350 List<TaskView> taskViews = mStackView.getTaskViews(); 351 int taskViewCount = taskViews.size(); 352 for (int i = taskViewCount - 1; i >= 0; i--) { 353 TaskView tv = taskViews.get(i); 354 Task task = tv.getTask(); 355 TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i, 356 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN, 357 postAnimationTrigger.decrementOnAnimationEnd()); 358 postAnimationTrigger.increment(); 359 360 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 361 null); 362 mTmpTransform.alpha = 0f; 363 mTmpTransform.rect.offsetTo(mTmpTransform.rect.left, offscreenY); 364 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 365 } 366 } 367 368 /** 369 * Starts the animation to show the {@link TaskView}s when the history is hidden. 370 */ 371 public void startHideHistoryAnimation() { 372 Resources res = mStackView.getResources(); 373 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 374 TaskStackViewScroller stackScroller = mStackView.getScroller(); 375 376 int historyTransitionDuration = res.getInteger( 377 R.integer.recents_history_transition_duration); 378 int startDelayIncr = 16; 379 380 List<TaskView> taskViews = mStackView.getTaskViews(); 381 int taskViewCount = taskViews.size(); 382 for (int i = taskViewCount - 1; i >= 0; i--) { 383 TaskView tv = taskViews.get(i); 384 TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i, 385 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN); 386 stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(), 387 mTmpTransform, null); 388 mTmpTransform.alpha = 1f; 389 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 390 } 391 } 392} 393