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