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