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