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