TaskStackAnimationHelper.java revision 5df7667edbabdf8bf43bc08ef7df2859fd620314
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.animation.TimeInterpolator; 22import android.animation.ValueAnimator; 23import android.content.Context; 24import android.content.res.Configuration; 25import android.content.res.Resources; 26import android.util.Log; 27import android.view.View; 28import android.view.animation.Interpolator; 29import android.view.animation.PathInterpolator; 30 31import com.android.systemui.Interpolators; 32import com.android.systemui.R; 33import com.android.systemui.recents.Recents; 34import com.android.systemui.recents.RecentsActivityLaunchState; 35import com.android.systemui.recents.RecentsConfiguration; 36import com.android.systemui.recents.misc.ReferenceCountedTrigger; 37import com.android.systemui.recents.model.Task; 38import com.android.systemui.recents.model.TaskStack; 39 40import java.util.ArrayList; 41import java.util.List; 42 43/** 44 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView}, 45 * but not the contents of the {@link TaskView}s. 46 */ 47public class TaskStackAnimationHelper { 48 49 /** 50 * Callbacks from the helper to coordinate view-content animations with view animations. 51 */ 52 public interface Callbacks { 53 /** 54 * Callback to prepare for the start animation for the launch target {@link TaskView}. 55 */ 56 void onPrepareLaunchTargetForEnterAnimation(); 57 58 /** 59 * Callback to start the animation for the launch target {@link TaskView}. 60 */ 61 void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration, 62 boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger); 63 64 /** 65 * Callback to start the animation for the launch target {@link TaskView} when it is 66 * launched from Recents. 67 */ 68 void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, 69 ReferenceCountedTrigger postAnimationTrigger); 70 71 /** 72 * Callback to start the animation for the front {@link TaskView} if there is no launch 73 * target. 74 */ 75 void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled); 76 } 77 78 private static final int DOUBLE_FRAME_OFFSET_MS = 33; 79 private static final int FRAME_OFFSET_MS = 16; 80 81 private static final int ENTER_EXIT_NUM_ANIMATING_TASKS = 5; 82 83 private static final int ENTER_FROM_HOME_ALPHA_DURATION = 100; 84 public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 300; 85 private static final Interpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR = 86 Interpolators.LINEAR_OUT_SLOW_IN; 87 private static final Interpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = Interpolators.LINEAR; 88 89 public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 200; 90 private static final Interpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR = 91 new PathInterpolator(0.4f, 0, 0.6f, 1f); 92 93 private static final int DISMISS_TASK_DURATION = 175; 94 private static final int DISMISS_ALL_TASKS_DURATION = 200; 95 private static final Interpolator DISMISS_ALL_TRANSLATION_INTERPOLATOR = 96 new PathInterpolator(0.4f, 0, 1f, 1f); 97 98 private static final Interpolator FOCUS_NEXT_TASK_INTERPOLATOR = 99 new PathInterpolator(0.4f, 0, 0, 1f); 100 private static final Interpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR = 101 new PathInterpolator(0, 0, 0, 1f); 102 private static final Interpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR = 103 Interpolators.LINEAR_OUT_SLOW_IN; 104 105 private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR = 106 Interpolators.LINEAR_OUT_SLOW_IN; 107 108 private TaskStackView mStackView; 109 110 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 111 private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>(); 112 private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>(); 113 114 public TaskStackAnimationHelper(Context context, TaskStackView stackView) { 115 mStackView = stackView; 116 } 117 118 /** 119 * Prepares the stack views and puts them in their initial animation state while visible, before 120 * the in-app enter animations start (after the window-transition completes). 121 */ 122 public void prepareForEnterAnimation() { 123 RecentsConfiguration config = Recents.getConfiguration(); 124 RecentsActivityLaunchState launchState = config.getLaunchState(); 125 Resources res = mStackView.getResources(); 126 Resources appResources = mStackView.getContext().getApplicationContext().getResources(); 127 128 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 129 TaskStackViewScroller stackScroller = mStackView.getScroller(); 130 TaskStack stack = mStackView.getStack(); 131 Task launchTargetTask = stack.getLaunchTarget(); 132 133 // Break early if there are no tasks 134 if (stack.getTaskCount() == 0) { 135 return; 136 } 137 138 int offscreenYOffset = stackLayout.mStackRect.height(); 139 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 140 R.dimen.recents_task_stack_animation_affiliate_enter_offset); 141 int launchedWhileDockingOffset = res.getDimensionPixelSize( 142 R.dimen.recents_task_stack_animation_launched_while_docking_offset); 143 boolean isLandscape = appResources.getConfiguration().orientation 144 == Configuration.ORIENTATION_LANDSCAPE; 145 146 // Prepare each of the task views for their enter animation from front to back 147 List<TaskView> taskViews = mStackView.getTaskViews(); 148 for (int i = taskViews.size() - 1; i >= 0; i--) { 149 TaskView tv = taskViews.get(i); 150 Task task = tv.getTask(); 151 boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && 152 launchTargetTask.group != null && 153 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 154 boolean hideTask = launchTargetTask != null && 155 launchTargetTask.isFreeformTask() && 156 task.isFreeformTask(); 157 158 // Get the current transform for the task, which will be used to position it offscreen 159 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 160 null); 161 162 if (hideTask) { 163 tv.setVisibility(View.INVISIBLE); 164 } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { 165 if (task.isLaunchTarget) { 166 tv.onPrepareLaunchTargetForEnterAnimation(); 167 } else if (currentTaskOccludesLaunchTarget) { 168 // Move the task view slightly lower so we can animate it in 169 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 170 mTmpTransform.alpha = 0f; 171 mStackView.updateTaskViewToTransform(tv, mTmpTransform, 172 AnimationProps.IMMEDIATE); 173 tv.setClipViewInStack(false); 174 } 175 } else if (launchState.launchedFromHome) { 176 // Move the task view off screen (below) so we can animate it in 177 mTmpTransform.rect.offset(0, offscreenYOffset); 178 mTmpTransform.alpha = 0f; 179 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE); 180 } else if (launchState.launchedViaDockGesture) { 181 int offset = isLandscape 182 ? launchedWhileDockingOffset 183 : (int) (offscreenYOffset * 0.9f); 184 mTmpTransform.rect.offset(0, offset); 185 mTmpTransform.alpha = 0f; 186 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE); 187 } 188 } 189 } 190 191 /** 192 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places 193 * depending on how Recents was triggered. 194 */ 195 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { 196 RecentsConfiguration config = Recents.getConfiguration(); 197 RecentsActivityLaunchState launchState = config.getLaunchState(); 198 Resources res = mStackView.getResources(); 199 Resources appRes = mStackView.getContext().getApplicationContext().getResources(); 200 201 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 202 TaskStackViewScroller stackScroller = mStackView.getScroller(); 203 TaskStack stack = mStackView.getStack(); 204 Task launchTargetTask = stack.getLaunchTarget(); 205 206 // Break early if there are no tasks 207 if (stack.getTaskCount() == 0) { 208 return; 209 } 210 211 int taskViewEnterFromAppDuration = res.getInteger( 212 R.integer.recents_task_enter_from_app_duration); 213 int taskViewEnterFromAffiliatedAppDuration = res.getInteger( 214 R.integer.recents_task_enter_from_affiliated_app_duration); 215 int dockGestureAnimDuration = appRes.getInteger( 216 R.integer.long_press_dock_anim_duration); 217 218 // Create enter animations for each of the views from front to back 219 List<TaskView> taskViews = mStackView.getTaskViews(); 220 int taskViewCount = taskViews.size(); 221 for (int i = taskViewCount - 1; i >= 0; i--) { 222 int taskIndexFromFront = taskViewCount - i - 1; 223 int taskIndexFromBack = i; 224 final TaskView tv = taskViews.get(i); 225 Task task = tv.getTask(); 226 boolean currentTaskOccludesLaunchTarget = launchTargetTask != null && 227 launchTargetTask.group != null && 228 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); 229 230 // Get the current transform for the task, which will be updated to the final transform 231 // to animate to depending on how recents was invoked 232 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 233 null); 234 235 if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { 236 if (task.isLaunchTarget) { 237 tv.onStartLaunchTargetEnterAnimation(mTmpTransform, 238 taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, 239 postAnimationTrigger); 240 } else { 241 // Animate the task up if it was occluding the launch target 242 if (currentTaskOccludesLaunchTarget) { 243 AnimationProps taskAnimation = new AnimationProps( 244 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, 245 new AnimatorListenerAdapter() { 246 @Override 247 public void onAnimationEnd(Animator animation) { 248 postAnimationTrigger.decrement(); 249 tv.setClipViewInStack(true); 250 } 251 }); 252 postAnimationTrigger.increment(); 253 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 254 } 255 } 256 257 } else if (launchState.launchedFromHome) { 258 // Animate the tasks up, but offset the animations to be relative to the front-most 259 // task animation 260 AnimationProps taskAnimation = new AnimationProps() 261 .setInitialPlayTime(AnimationProps.BOUNDS, 262 Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) * 263 DOUBLE_FRAME_OFFSET_MS) 264 .setStartDelay(AnimationProps.ALPHA, 265 Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) * 266 FRAME_OFFSET_MS) 267 .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION) 268 .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION) 269 .setInterpolator(AnimationProps.BOUNDS, 270 ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR) 271 .setInterpolator(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_INTERPOLATOR) 272 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 273 postAnimationTrigger.increment(); 274 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 275 if (i == taskViewCount - 1) { 276 tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled); 277 } 278 } else if (launchState.launchedViaDockGesture) { 279 // Animate the tasks up - add some delay to match the divider animation 280 AnimationProps taskAnimation = new AnimationProps() 281 .setDuration(AnimationProps.BOUNDS, dockGestureAnimDuration + 282 (taskIndexFromBack * DOUBLE_FRAME_OFFSET_MS)) 283 .setInterpolator(AnimationProps.BOUNDS, 284 ENTER_WHILE_DOCKING_INTERPOLATOR) 285 .setStartDelay(AnimationProps.BOUNDS, 48) 286 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 287 postAnimationTrigger.increment(); 288 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 289 } 290 } 291 } 292 293 /** 294 * Starts an in-app animation to hide all the task views so that we can transition back home. 295 */ 296 public void startExitToHomeAnimation(boolean animated, 297 ReferenceCountedTrigger postAnimationTrigger) { 298 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 299 TaskStack stack = mStackView.getStack(); 300 301 // Break early if there are no tasks 302 if (stack.getTaskCount() == 0) { 303 return; 304 } 305 306 int offscreenYOffset = stackLayout.mStackRect.height(); 307 308 // Create the animations for each of the tasks 309 List<TaskView> taskViews = mStackView.getTaskViews(); 310 int taskViewCount = taskViews.size(); 311 for (int i = 0; i < taskViewCount; i++) { 312 int taskIndexFromFront = taskViewCount - i - 1; 313 TaskView tv = taskViews.get(i); 314 Task task = tv.getTask(); 315 316 if (mStackView.isIgnoredTask(task)) { 317 continue; 318 } 319 320 // Animate the tasks down 321 AnimationProps taskAnimation; 322 if (animated) { 323 int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) * 324 DOUBLE_FRAME_OFFSET_MS; 325 taskAnimation = new AnimationProps() 326 .setStartDelay(AnimationProps.BOUNDS, delay) 327 .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION) 328 .setInterpolator(AnimationProps.BOUNDS, 329 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR) 330 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 331 postAnimationTrigger.increment(); 332 } else { 333 taskAnimation = AnimationProps.IMMEDIATE; 334 } 335 336 mTmpTransform.fillIn(tv); 337 mTmpTransform.rect.offset(0, offscreenYOffset); 338 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 339 } 340 } 341 342 /** 343 * Starts the animation for the launching task view, hiding any tasks that might occlude the 344 * window transition for the launching task. 345 */ 346 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, 347 final ReferenceCountedTrigger postAnimationTrigger) { 348 Resources res = mStackView.getResources(); 349 350 int taskViewExitToAppDuration = res.getInteger( 351 R.integer.recents_task_exit_to_app_duration); 352 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 353 R.dimen.recents_task_stack_animation_affiliate_enter_offset); 354 355 Task launchingTask = launchingTaskView.getTask(); 356 List<TaskView> taskViews = mStackView.getTaskViews(); 357 int taskViewCount = taskViews.size(); 358 for (int i = 0; i < taskViewCount; i++) { 359 TaskView tv = taskViews.get(i); 360 Task task = tv.getTask(); 361 boolean currentTaskOccludesLaunchTarget = launchingTask != null && 362 launchingTask.group != null && 363 launchingTask.group.isTaskAboveTask(task, launchingTask); 364 365 if (tv == launchingTaskView) { 366 tv.setClipViewInStack(false); 367 postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 368 @Override 369 public void run() { 370 tv.setClipViewInStack(true); 371 } 372 }); 373 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, 374 screenPinningRequested, postAnimationTrigger); 375 } else if (currentTaskOccludesLaunchTarget) { 376 // Animate this task out of view 377 AnimationProps taskAnimation = new AnimationProps( 378 taskViewExitToAppDuration, Interpolators.ALPHA_OUT, 379 postAnimationTrigger.decrementOnAnimationEnd()); 380 postAnimationTrigger.increment(); 381 382 mTmpTransform.fillIn(tv); 383 mTmpTransform.alpha = 0f; 384 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 385 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 386 } 387 } 388 } 389 390 /** 391 * Starts the delete animation for the specified {@link TaskView}. 392 */ 393 public void startDeleteTaskAnimation(final TaskView deleteTaskView, boolean gridLayout, 394 final ReferenceCountedTrigger postAnimationTrigger) { 395 if (gridLayout) { 396 startTaskGridDeleteTaskAnimation(deleteTaskView, postAnimationTrigger); 397 } else { 398 startTaskStackDeleteTaskAnimation(deleteTaskView, postAnimationTrigger); 399 } 400 } 401 402 /** 403 * Starts the delete animation for all the {@link TaskView}s. 404 */ 405 public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, boolean gridLayout, 406 final ReferenceCountedTrigger postAnimationTrigger) { 407 if (gridLayout) { 408 for (int i = 0; i < taskViews.size(); i++) { 409 startTaskGridDeleteTaskAnimation(taskViews.get(i), postAnimationTrigger); 410 } 411 } else { 412 startTaskStackDeleteAllTasksAnimation(taskViews, postAnimationTrigger); 413 } 414 } 415 416 /** 417 * Starts the animation to focus the next {@link TaskView} when paging through recents. 418 * 419 * @return whether or not this will trigger a scroll in the stack 420 */ 421 public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask, 422 boolean requestViewFocus) { 423 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 424 TaskStackViewScroller stackScroller = mStackView.getScroller(); 425 TaskStack stack = mStackView.getStack(); 426 427 final float curScroll = stackScroller.getStackScroll(); 428 final float newScroll = stackScroller.getBoundedStackScroll( 429 stackLayout.getStackScrollForTask(newFocusedTask)); 430 boolean willScrollToFront = newScroll > curScroll; 431 boolean willScroll = Float.compare(newScroll, curScroll) != 0; 432 433 // Get the current set of task transforms 434 int taskViewCount = mStackView.getTaskViews().size(); 435 ArrayList<Task> stackTasks = stack.getStackTasks(); 436 mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); 437 438 // Pick up the newly visible views after the scroll 439 mStackView.bindVisibleTaskViews(newScroll); 440 441 // Update the internal state 442 stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED); 443 stackScroller.setStackScroll(newScroll, null /* animation */); 444 mStackView.cancelDeferredTaskViewLayoutAnimation(); 445 446 // Get the final set of task transforms 447 mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks, 448 true /* ignoreTaskOverrides */, mTmpFinalTaskTransforms); 449 450 // Focus the task view 451 TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask); 452 if (newFocusedTaskView == null) { 453 // Log the error if we have no task view, and skip the animation 454 Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount + 455 " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll + 456 " postscroll: " + newScroll); 457 return false; 458 } 459 newFocusedTaskView.setFocusedState(true, requestViewFocus); 460 461 // Setup the end listener to return all the hidden views to the view pool after the 462 // focus animation 463 ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger(); 464 postAnimTrigger.addLastDecrementRunnable(new Runnable() { 465 @Override 466 public void run() { 467 mStackView.bindVisibleTaskViews(newScroll); 468 } 469 }); 470 471 List<TaskView> taskViews = mStackView.getTaskViews(); 472 taskViewCount = taskViews.size(); 473 int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView); 474 for (int i = 0; i < taskViewCount; i++) { 475 TaskView tv = taskViews.get(i); 476 Task task = tv.getTask(); 477 478 if (mStackView.isIgnoredTask(task)) { 479 continue; 480 } 481 482 int taskIndex = stackTasks.indexOf(task); 483 TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex); 484 TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex); 485 486 // Update the task to the initial state (for the newly picked up tasks) 487 mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE); 488 489 int duration; 490 Interpolator interpolator; 491 if (willScrollToFront) { 492 duration = calculateStaggeredAnimDuration(i); 493 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; 494 } else { 495 if (i < newFocusTaskViewIndex) { 496 duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50); 497 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; 498 } else if (i > newFocusTaskViewIndex) { 499 duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50)); 500 interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR; 501 } else { 502 duration = 200; 503 interpolator = FOCUS_NEXT_TASK_INTERPOLATOR; 504 } 505 } 506 507 AnimationProps anim = new AnimationProps() 508 .setDuration(AnimationProps.BOUNDS, duration) 509 .setInterpolator(AnimationProps.BOUNDS, interpolator) 510 .setListener(postAnimTrigger.decrementOnAnimationEnd()); 511 postAnimTrigger.increment(); 512 mStackView.updateTaskViewToTransform(tv, toTransform, anim); 513 } 514 return willScroll; 515 } 516 517 /** 518 * Starts the animation to go to the initial stack layout with a task focused. In addition, the 519 * previous task will be animated in after the scroll completes. 520 */ 521 public void startNewStackScrollAnimation(TaskStack newStack, 522 ReferenceCountedTrigger animationTrigger) { 523 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 524 TaskStackViewScroller stackScroller = mStackView.getScroller(); 525 526 // Get the current set of task transforms 527 ArrayList<Task> stackTasks = newStack.getStackTasks(); 528 mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); 529 530 // Update the stack 531 mStackView.setTasks(newStack, false /* allowNotifyStackChanges */); 532 mStackView.updateLayoutAlgorithm(false /* boundScroll */); 533 534 // Pick up the newly visible views after the scroll 535 final float newScroll = stackLayout.mInitialScrollP; 536 mStackView.bindVisibleTaskViews(newScroll); 537 538 // Update the internal state 539 stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED); 540 stackLayout.setTaskOverridesForInitialState(newStack, true /* ignoreScrollToFront */); 541 stackScroller.setStackScroll(newScroll); 542 mStackView.cancelDeferredTaskViewLayoutAnimation(); 543 544 // Get the final set of task transforms 545 mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks, 546 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms); 547 548 // Hide the front most task view until the scroll is complete 549 Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */); 550 final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask); 551 final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get( 552 stackTasks.indexOf(frontMostTask)); 553 if (frontMostTaskView != null) { 554 mStackView.updateTaskViewToTransform(frontMostTaskView, 555 stackLayout.getFrontOfStackTransform(), AnimationProps.IMMEDIATE); 556 } 557 558 // Setup the end listener to return all the hidden views to the view pool after the 559 // focus animation 560 animationTrigger.addLastDecrementRunnable(new Runnable() { 561 @Override 562 public void run() { 563 mStackView.bindVisibleTaskViews(newScroll); 564 565 // Now, animate in the front-most task 566 if (frontMostTaskView != null) { 567 mStackView.updateTaskViewToTransform(frontMostTaskView, frontMostTransform, 568 new AnimationProps(75, 250, FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR)); 569 } 570 } 571 }); 572 573 List<TaskView> taskViews = mStackView.getTaskViews(); 574 int taskViewCount = taskViews.size(); 575 for (int i = 0; i < taskViewCount; i++) { 576 TaskView tv = taskViews.get(i); 577 Task task = tv.getTask(); 578 579 if (mStackView.isIgnoredTask(task)) { 580 continue; 581 } 582 if (task == frontMostTask && frontMostTaskView != null) { 583 continue; 584 } 585 586 int taskIndex = stackTasks.indexOf(task); 587 TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex); 588 TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex); 589 590 // Update the task to the initial state (for the newly picked up tasks) 591 mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE); 592 593 int duration = calculateStaggeredAnimDuration(i); 594 Interpolator interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; 595 596 AnimationProps anim = new AnimationProps() 597 .setDuration(AnimationProps.BOUNDS, duration) 598 .setInterpolator(AnimationProps.BOUNDS, interpolator) 599 .setListener(animationTrigger.decrementOnAnimationEnd()); 600 animationTrigger.increment(); 601 mStackView.updateTaskViewToTransform(tv, toTransform, anim); 602 } 603 } 604 605 /** 606 * Caclulates a staggered duration for {@link #startScrollToFocusedTaskAnimation} and 607 * {@link #startNewStackScrollAnimation}. 608 */ 609 private int calculateStaggeredAnimDuration(int i) { 610 return Math.max(100, 100 + ((i - 1) * 50)); 611 } 612 613 private void startTaskGridDeleteTaskAnimation(final TaskView deleteTaskView, 614 final ReferenceCountedTrigger postAnimationTrigger) { 615 postAnimationTrigger.increment(); 616 postAnimationTrigger.addLastDecrementRunnable(() -> { 617 mStackView.getTouchHandler().onChildDismissed(deleteTaskView); 618 }); 619 deleteTaskView.animate().setDuration(300).scaleX(0).scaleY(0).alpha(0).setListener( 620 new AnimatorListenerAdapter() { 621 @Override 622 public void onAnimationEnd(Animator animation) { 623 postAnimationTrigger.decrement(); 624 }}).start(); 625 } 626 627 private void startTaskStackDeleteTaskAnimation(final TaskView deleteTaskView, 628 final ReferenceCountedTrigger postAnimationTrigger) { 629 TaskStackViewTouchHandler touchHandler = mStackView.getTouchHandler(); 630 touchHandler.onBeginManualDrag(deleteTaskView); 631 632 postAnimationTrigger.increment(); 633 postAnimationTrigger.addLastDecrementRunnable(() -> { 634 touchHandler.onChildDismissed(deleteTaskView); 635 }); 636 637 final float dismissSize = touchHandler.getScaledDismissSize(); 638 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); 639 animator.setDuration(400); 640 animator.addUpdateListener((animation) -> { 641 float progress = (Float) animation.getAnimatedValue(); 642 deleteTaskView.setTranslationX(progress * dismissSize); 643 touchHandler.updateSwipeProgress(deleteTaskView, true, progress); 644 }); 645 animator.addListener(new AnimatorListenerAdapter() { 646 @Override 647 public void onAnimationEnd(Animator animation) { 648 postAnimationTrigger.decrement(); 649 } 650 }); 651 animator.start(); 652 } 653 654 private void startTaskStackDeleteAllTasksAnimation(final List<TaskView> taskViews, 655 final ReferenceCountedTrigger postAnimationTrigger) { 656 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 657 658 int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.getTaskRect().left; 659 660 int taskViewCount = taskViews.size(); 661 for (int i = taskViewCount - 1; i >= 0; i--) { 662 TaskView tv = taskViews.get(i); 663 int taskIndexFromFront = taskViewCount - i - 1; 664 int startDelay = taskIndexFromFront * DOUBLE_FRAME_OFFSET_MS; 665 666 // Disabling clipping with the stack while the view is animating away 667 tv.setClipViewInStack(false); 668 669 // Compose the new animation and transform and star the animation 670 AnimationProps taskAnimation = new AnimationProps(startDelay, 671 DISMISS_ALL_TASKS_DURATION, DISMISS_ALL_TRANSLATION_INTERPOLATOR, 672 new AnimatorListenerAdapter() { 673 @Override 674 public void onAnimationEnd(Animator animation) { 675 postAnimationTrigger.decrement(); 676 677 // Re-enable clipping with the stack (we will reuse this view) 678 tv.setClipViewInStack(true); 679 } 680 }); 681 postAnimationTrigger.increment(); 682 683 mTmpTransform.fillIn(tv); 684 mTmpTransform.rect.offset(offscreenXOffset, 0); 685 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 686 } 687 } 688} 689