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