TaskStackAnimationHelper.java revision 899327f5cbbfb0eae5562b262ccea860c98f6bc4
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.content.Context; 22import android.content.res.Resources; 23import android.graphics.RectF; 24import android.util.Log; 25import android.view.View; 26import android.view.animation.Interpolator; 27import android.view.animation.PathInterpolator; 28 29import com.android.systemui.Interpolators; 30import com.android.systemui.R; 31import com.android.systemui.recents.Recents; 32import com.android.systemui.recents.RecentsActivityLaunchState; 33import com.android.systemui.recents.RecentsConfiguration; 34import com.android.systemui.recents.misc.ReferenceCountedTrigger; 35import com.android.systemui.recents.model.Task; 36import com.android.systemui.recents.model.TaskStack; 37 38import java.util.ArrayList; 39import java.util.List; 40 41/** 42 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView}, 43 * but not the contents of the {@link TaskView}s. 44 */ 45public class TaskStackAnimationHelper { 46 47 /** 48 * Callbacks from the helper to coordinate view-content animations with view animations. 49 */ 50 public interface Callbacks { 51 /** 52 * Callback to prepare for the start animation for the launch target {@link TaskView}. 53 */ 54 void onPrepareLaunchTargetForEnterAnimation(); 55 56 /** 57 * Callback to start the animation for the launch target {@link TaskView}. 58 */ 59 void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration, 60 boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger); 61 62 /** 63 * Callback to start the animation for the launch target {@link TaskView} when it is 64 * launched from Recents. 65 */ 66 void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, 67 ReferenceCountedTrigger postAnimationTrigger); 68 } 69 70 private static final int FRAME_OFFSET_MS = 16; 71 72 public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100; 73 public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333; 74 public static final int ENTER_WHILE_DOCKING_DURATION = 150; 75 76 private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR = 77 new PathInterpolator(0, 0, 0, 1f); 78 private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = 79 new PathInterpolator(0, 0, 0.2f, 1f); 80 81 public static final int EXIT_TO_HOME_ALPHA_DURATION = 100; 82 public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150; 83 private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR = 84 new PathInterpolator(0.8f, 0, 0.6f, 1f); 85 private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR = 86 new PathInterpolator(0.4f, 0, 1f, 1f); 87 88 private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR = 89 new PathInterpolator(0.4f, 0, 0, 1f); 90 private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR = 91 new PathInterpolator(0, 0, 0, 1f); 92 private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR = 93 new PathInterpolator(0.4f, 0, 0.2f, 1f); 94 95 private static final PathInterpolator ENTER_WHILE_DOCKING_INTERPOLATOR = 96 new PathInterpolator(0, 0, 0.2f, 1f); 97 98 private TaskStackView mStackView; 99 100 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 101 private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>(); 102 private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>(); 103 104 public TaskStackAnimationHelper(Context context, TaskStackView stackView) { 105 mStackView = stackView; 106 } 107 108 /** 109 * Prepares the stack views and puts them in their initial animation state while visible, before 110 * the in-app enter animations start (after the window-transition completes). 111 */ 112 public void prepareForEnterAnimation() { 113 RecentsConfiguration config = Recents.getConfiguration(); 114 RecentsActivityLaunchState launchState = config.getLaunchState(); 115 Resources res = mStackView.getResources(); 116 117 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 118 TaskStackViewScroller stackScroller = mStackView.getScroller(); 119 TaskStack stack = mStackView.getStack(); 120 Task launchTargetTask = stack.getLaunchTarget(); 121 122 // Break early if there are no tasks 123 if (stack.getTaskCount() == 0) { 124 return; 125 } 126 127 int offscreenYOffset = stackLayout.mStackRect.height(); 128 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 129 R.dimen.recents_task_view_affiliate_group_enter_offset); 130 int launchedWhileDockingOffset = res.getDimensionPixelSize( 131 R.dimen.recents_task_view_launched_while_docking_offset); 132 133 // Prepare each of the task views for their enter animation from front to back 134 List<TaskView> taskViews = mStackView.getTaskViews(); 135 for (int i = taskViews.size() - 1; i >= 0; i--) { 136 TaskView tv = taskViews.get(i); 137 Task task = tv.getTask(); 138 boolean currentTaskOccludesLaunchTarget = (launchTargetTask != null && 139 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask)); 140 boolean hideTask = (launchTargetTask != null && 141 launchTargetTask.isFreeformTask() && task.isFreeformTask()); 142 143 // Get the current transform for the task, which will be used to position it offscreen 144 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 145 null); 146 147 if (hideTask) { 148 tv.setVisibility(View.INVISIBLE); 149 } else if (launchState.launchedHasConfigurationChanged) { 150 // Just load the views as-is 151 } else if (launchState.launchedFromApp && !launchState.launchedWhileDocking) { 152 if (task.isLaunchTarget) { 153 tv.onPrepareLaunchTargetForEnterAnimation(); 154 } else if (currentTaskOccludesLaunchTarget) { 155 // Move the task view slightly lower so we can animate it in 156 RectF bounds = new RectF(mTmpTransform.rect); 157 bounds.offset(0, taskViewAffiliateGroupEnterOffset); 158 tv.setClipViewInStack(false); 159 tv.setAlpha(0f); 160 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, 161 (int) bounds.right, (int) bounds.bottom); 162 } 163 } else if (launchState.launchedFromHome) { 164 // Move the task view off screen (below) so we can animate it in 165 RectF bounds = new RectF(mTmpTransform.rect); 166 bounds.offset(0, offscreenYOffset); 167 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, 168 (int) bounds.bottom); 169 } else if (launchState.launchedWhileDocking) { 170 RectF bounds = new RectF(mTmpTransform.rect); 171 bounds.offset(0, launchedWhileDockingOffset); 172 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, 173 (int) bounds.bottom); 174 } 175 } 176 } 177 178 /** 179 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places 180 * depending on how Recents was triggered. 181 */ 182 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { 183 RecentsConfiguration config = Recents.getConfiguration(); 184 RecentsActivityLaunchState launchState = config.getLaunchState(); 185 Resources res = mStackView.getResources(); 186 187 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 188 TaskStackViewScroller stackScroller = mStackView.getScroller(); 189 TaskStack stack = mStackView.getStack(); 190 Task launchTargetTask = stack.getLaunchTarget(); 191 192 // Break early if there are no tasks 193 if (stack.getTaskCount() == 0) { 194 return; 195 } 196 197 int taskViewEnterFromAppDuration = res.getInteger( 198 R.integer.recents_task_enter_from_app_duration); 199 int taskViewEnterFromAffiliatedAppDuration = res.getInteger( 200 R.integer.recents_task_enter_from_affiliated_app_duration); 201 202 // Create enter animations for each of the views from front to back 203 List<TaskView> taskViews = mStackView.getTaskViews(); 204 int taskViewCount = taskViews.size(); 205 for (int i = taskViewCount - 1; i >= 0; i--) { 206 int taskIndexFromFront = taskViewCount - i - 1; 207 int taskIndexFromBack = i; 208 final TaskView tv = taskViews.get(i); 209 Task task = tv.getTask(); 210 boolean currentTaskOccludesLaunchTarget = false; 211 if (launchTargetTask != null) { 212 currentTaskOccludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, 213 launchTargetTask); 214 } 215 216 // Get the current transform for the task, which will be updated to the final transform 217 // to animate to depending on how recents was invoked 218 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 219 null); 220 221 if (launchState.launchedFromApp && !launchState.launchedWhileDocking) { 222 if (task.isLaunchTarget) { 223 tv.onStartLaunchTargetEnterAnimation(mTmpTransform, 224 taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, 225 postAnimationTrigger); 226 } else { 227 // Animate the task up if it was occluding the launch target 228 if (currentTaskOccludesLaunchTarget) { 229 AnimationProps taskAnimation = new AnimationProps( 230 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, 231 new AnimatorListenerAdapter() { 232 @Override 233 public void onAnimationEnd(Animator animation) { 234 postAnimationTrigger.decrement(); 235 tv.setClipViewInStack(false); 236 } 237 }); 238 postAnimationTrigger.increment(); 239 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 240 } 241 } 242 243 } else if (launchState.launchedFromHome) { 244 // Animate the tasks up 245 AnimationProps taskAnimation = new AnimationProps() 246 .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS) 247 .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION) 248 .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION - 249 (taskIndexFromFront * FRAME_OFFSET_MS)) 250 .setInterpolator(AnimationProps.BOUNDS, 251 ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR) 252 .setInterpolator(AnimationProps.ALPHA, 253 ENTER_FROM_HOME_ALPHA_INTERPOLATOR) 254 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 255 postAnimationTrigger.increment(); 256 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 257 } else if (launchState.launchedWhileDocking) { 258 // Animate the tasks up 259 AnimationProps taskAnimation = new AnimationProps() 260 .setDuration(AnimationProps.BOUNDS, (int) (ENTER_WHILE_DOCKING_DURATION + 261 (taskIndexFromBack * 2f * FRAME_OFFSET_MS))) 262 .setInterpolator(AnimationProps.BOUNDS, 263 ENTER_WHILE_DOCKING_INTERPOLATOR) 264 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 265 postAnimationTrigger.increment(); 266 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 267 } 268 } 269 } 270 271 /** 272 * Starts an in-app animation to hide all the task views so that we can transition back home. 273 */ 274 public void startExitToHomeAnimation(boolean animated, 275 ReferenceCountedTrigger postAnimationTrigger) { 276 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 277 TaskStackViewScroller stackScroller = mStackView.getScroller(); 278 TaskStack stack = mStackView.getStack(); 279 280 // Break early if there are no tasks 281 if (stack.getTaskCount() == 0) { 282 return; 283 } 284 285 int offscreenYOffset = stackLayout.mStackRect.height(); 286 287 // Create the animations for each of the tasks 288 List<TaskView> taskViews = mStackView.getTaskViews(); 289 int taskViewCount = taskViews.size(); 290 for (int i = 0; i < taskViewCount; i++) { 291 int taskIndexFromFront = taskViewCount - i - 1; 292 TaskView tv = taskViews.get(i); 293 Task task = tv.getTask(); 294 295 // Animate the tasks down 296 AnimationProps taskAnimation; 297 if (animated) { 298 taskAnimation = new AnimationProps() 299 .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS) 300 .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION) 301 .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION + 302 (taskIndexFromFront * FRAME_OFFSET_MS)) 303 .setInterpolator(AnimationProps.BOUNDS, 304 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR) 305 .setInterpolator(AnimationProps.ALPHA, 306 EXIT_TO_HOME_ALPHA_INTERPOLATOR) 307 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 308 postAnimationTrigger.increment(); 309 } else { 310 taskAnimation = AnimationProps.IMMEDIATE; 311 } 312 313 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 314 null); 315 mTmpTransform.rect.offset(0, offscreenYOffset); 316 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 317 } 318 } 319 320 /** 321 * Starts the animation for the launching task view, hiding any tasks that might occlude the 322 * window transition for the launching task. 323 */ 324 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, 325 final ReferenceCountedTrigger postAnimationTrigger) { 326 Resources res = mStackView.getResources(); 327 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 328 TaskStackViewScroller stackScroller = mStackView.getScroller(); 329 330 int taskViewExitToAppDuration = res.getInteger( 331 R.integer.recents_task_exit_to_app_duration); 332 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 333 R.dimen.recents_task_view_affiliate_group_enter_offset); 334 335 Task launchingTask = launchingTaskView.getTask(); 336 List<TaskView> taskViews = mStackView.getTaskViews(); 337 int taskViewCount = taskViews.size(); 338 for (int i = 0; i < taskViewCount; i++) { 339 TaskView tv = taskViews.get(i); 340 Task task = tv.getTask(); 341 boolean currentTaskOccludesLaunchTarget = (launchingTask != null && 342 launchingTask.group.isTaskAboveTask(task, launchingTask)); 343 344 if (tv == launchingTaskView) { 345 tv.setClipViewInStack(false); 346 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, 347 screenPinningRequested, postAnimationTrigger); 348 } else if (currentTaskOccludesLaunchTarget) { 349 // Animate this task out of view 350 AnimationProps taskAnimation = new AnimationProps( 351 taskViewExitToAppDuration, Interpolators.ALPHA_OUT, 352 postAnimationTrigger.decrementOnAnimationEnd()); 353 postAnimationTrigger.increment(); 354 355 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 356 null); 357 mTmpTransform.alpha = 0f; 358 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 359 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 360 } 361 } 362 } 363 364 /** 365 * Starts the delete animation for the specified {@link TaskView}. 366 */ 367 public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView, 368 final ReferenceCountedTrigger postAnimationTrigger) { 369 Resources res = mStackView.getResources(); 370 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 371 TaskStackViewScroller stackScroller = mStackView.getScroller(); 372 373 int taskViewRemoveAnimDuration = res.getInteger( 374 R.integer.recents_animate_task_view_remove_duration); 375 int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize( 376 R.dimen.recents_task_view_remove_anim_translation_x); 377 378 // Disabling clipping with the stack while the view is animating away 379 deleteTaskView.setClipViewInStack(false); 380 381 // Compose the new animation and transform and star the animation 382 AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration, 383 Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { 384 @Override 385 public void onAnimationEnd(Animator animation) { 386 postAnimationTrigger.decrement(); 387 388 // Re-enable clipping with the stack (we will reuse this view) 389 deleteTaskView.setClipViewInStack(true); 390 } 391 }); 392 postAnimationTrigger.increment(); 393 394 stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform, 395 null); 396 mTmpTransform.alpha = 0f; 397 mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0); 398 mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); 399 } 400 401 /** 402 * Starts the animation to hide the {@link TaskView}s when the history is shown. 403 */ 404 public void startShowHistoryAnimation(ReferenceCountedTrigger postAnimationTrigger) { 405 Resources res = mStackView.getResources(); 406 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 407 TaskStackViewScroller stackScroller = mStackView.getScroller(); 408 409 int offscreenY = stackLayout.mStackRect.bottom; 410 int historyTransitionDuration = res.getInteger( 411 R.integer.recents_history_transition_duration); 412 int startDelayIncr = 16; 413 414 List<TaskView> taskViews = mStackView.getTaskViews(); 415 int taskViewCount = taskViews.size(); 416 for (int i = taskViewCount - 1; i >= 0; i--) { 417 TaskView tv = taskViews.get(i); 418 Task task = tv.getTask(); 419 AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, 420 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN, 421 postAnimationTrigger.decrementOnAnimationEnd()); 422 postAnimationTrigger.increment(); 423 424 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 425 null); 426 mTmpTransform.alpha = 0f; 427 mTmpTransform.rect.offsetTo(mTmpTransform.rect.left, offscreenY); 428 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 429 } 430 } 431 432 /** 433 * Starts the animation to show the {@link TaskView}s when the history is hidden. 434 */ 435 public void startHideHistoryAnimation() { 436 Resources res = mStackView.getResources(); 437 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 438 TaskStackViewScroller stackScroller = mStackView.getScroller(); 439 440 int historyTransitionDuration = res.getInteger( 441 R.integer.recents_history_transition_duration); 442 int startDelayIncr = 16; 443 444 List<TaskView> taskViews = mStackView.getTaskViews(); 445 int taskViewCount = taskViews.size(); 446 for (int i = taskViewCount - 1; i >= 0; i--) { 447 TaskView tv = taskViews.get(i); 448 AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, 449 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN); 450 stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(), 451 mTmpTransform, null); 452 mTmpTransform.alpha = 1f; 453 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 454 } 455 } 456 457 /** 458 * Starts the animation to focus the next {@link TaskView} when paging through recents. 459 * 460 * @return whether or not this will trigger a scroll in the stack 461 */ 462 public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask, 463 boolean requestViewFocus) { 464 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 465 TaskStackViewScroller stackScroller = mStackView.getScroller(); 466 TaskStack stack = mStackView.getStack(); 467 468 final float curScroll = stackScroller.getStackScroll(); 469 final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask); 470 boolean willScrollToFront = newScroll > curScroll; 471 boolean willScroll = Float.compare(newScroll, curScroll) != 0; 472 473 // Get the current set of task transforms 474 int taskViewCount = mStackView.getTaskViews().size(); 475 ArrayList<Task> stackTasks = stack.getStackTasks(); 476 mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms); 477 478 // Pick up the newly visible views after the scroll 479 mStackView.bindVisibleTaskViews(newScroll); 480 481 // Update the internal state 482 stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED); 483 stackScroller.setStackScroll(newScroll, null /* animation */); 484 mStackView.cancelDeferredTaskViewLayoutAnimation(); 485 486 // Get the final set of task transforms 487 mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks, 488 mTmpFinalTaskTransforms); 489 490 // Focus the task view 491 TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask); 492 if (newFocusedTaskView == null) { 493 // Log the error if we have no task view, and skip the animation 494 Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount + 495 " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll + 496 " postscroll: " + newScroll); 497 return false; 498 } 499 newFocusedTaskView.setFocusedState(true, requestViewFocus); 500 501 // Setup the end listener to return all the hidden views to the view pool after the 502 // focus animation 503 ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger(); 504 postAnimTrigger.addLastDecrementRunnable(new Runnable() { 505 @Override 506 public void run() { 507 mStackView.bindVisibleTaskViews(newScroll); 508 } 509 }); 510 511 List<TaskView> taskViews = mStackView.getTaskViews(); 512 taskViewCount = taskViews.size(); 513 int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView); 514 for (int i = 0; i < taskViewCount; i++) { 515 TaskView tv = taskViews.get(i); 516 Task task = tv.getTask(); 517 518 if (mStackView.isIgnoredTask(task)) { 519 continue; 520 } 521 522 int taskIndex = stackTasks.indexOf(task); 523 TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex); 524 TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex); 525 526 // Update the task to the initial state (for the newly picked up tasks) 527 mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE); 528 529 int duration; 530 Interpolator interpolator; 531 if (willScrollToFront) { 532 duration = Math.max(100, 100 + ((i - 1) * 50)); 533 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; 534 } else { 535 if (i < newFocusTaskViewIndex) { 536 duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50); 537 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR; 538 } else if (i > newFocusTaskViewIndex) { 539 duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50)); 540 interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR; 541 } else { 542 duration = 200; 543 interpolator = FOCUS_NEXT_TASK_INTERPOLATOR; 544 } 545 } 546 547 AnimationProps anim = new AnimationProps() 548 .setDuration(AnimationProps.BOUNDS, duration) 549 .setInterpolator(AnimationProps.BOUNDS, interpolator) 550 .setListener(postAnimTrigger.decrementOnAnimationEnd()); 551 postAnimTrigger.increment(); 552 mStackView.updateTaskViewToTransform(tv, toTransform, anim); 553 } 554 return willScroll; 555 } 556} 557