TaskStackAnimationHelper.java revision e370e15056d45eba83d6ad96d9be46a17eddff29
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.Configuration; 23import android.content.res.Resources; 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.misc.Utilities; 36import com.android.systemui.recents.model.Task; 37import com.android.systemui.recents.model.TaskStack; 38 39import java.util.ArrayList; 40import java.util.List; 41 42/** 43 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView}, 44 * but not the contents of the {@link TaskView}s. 45 */ 46public class TaskStackAnimationHelper { 47 48 /** 49 * Callbacks from the helper to coordinate view-content animations with view animations. 50 */ 51 public interface Callbacks { 52 /** 53 * Callback to prepare for the start animation for the launch target {@link TaskView}. 54 */ 55 void onPrepareLaunchTargetForEnterAnimation(); 56 57 /** 58 * Callback to start the animation for the launch target {@link TaskView}. 59 */ 60 void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration, 61 boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger); 62 63 /** 64 * Callback to start the animation for the launch target {@link TaskView} when it is 65 * launched from Recents. 66 */ 67 void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, 68 ReferenceCountedTrigger postAnimationTrigger); 69 70 /** 71 * Callback to start the animation for the front {@link TaskView} if there is no launch 72 * target. 73 */ 74 void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled); 75 } 76 77 private static final int FRAME_OFFSET_MS = 16; 78 79 public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100; 80 public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333; 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 Resources appResources = mStackView.getContext().getApplicationContext().getResources(); 123 124 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 125 TaskStackViewScroller stackScroller = mStackView.getScroller(); 126 TaskStack stack = mStackView.getStack(); 127 Task launchTargetTask = stack.getLaunchTarget(); 128 129 // Break early if there are no tasks 130 if (stack.getTaskCount() == 0) { 131 return; 132 } 133 134 int offscreenYOffset = stackLayout.mStackRect.height(); 135 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 136 R.dimen.recents_task_stack_animation_affiliate_enter_offset); 137 int launchedWhileDockingOffset = res.getDimensionPixelSize( 138 R.dimen.recents_task_stack_animation_launched_while_docking_offset); 139 boolean isLandscape = appResources.getConfiguration().orientation 140 == Configuration.ORIENTATION_LANDSCAPE; 141 142 // Prepare each of the task views for their enter animation from front to back 143 List<TaskView> taskViews = mStackView.getTaskViews(); 144 for (int i = taskViews.size() - 1; i >= 0; i--) { 145 TaskView tv = taskViews.get(i); 146 Task task = tv.getTask(); 147 boolean currentTaskOccludesLaunchTarget = (launchTargetTask != null && 148 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask)); 149 boolean hideTask = (launchTargetTask != null && 150 launchTargetTask.isFreeformTask() && task.isFreeformTask()); 151 152 // Get the current transform for the task, which will be used to position it offscreen 153 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 154 null); 155 156 if (hideTask) { 157 tv.setVisibility(View.INVISIBLE); 158 } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { 159 if (task.isLaunchTarget) { 160 tv.onPrepareLaunchTargetForEnterAnimation(); 161 } else if (currentTaskOccludesLaunchTarget) { 162 // Move the task view slightly lower so we can animate it in 163 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 164 mTmpTransform.alpha = 0f; 165 mStackView.updateTaskViewToTransform(tv, mTmpTransform, 166 AnimationProps.IMMEDIATE); 167 tv.setClipViewInStack(false); 168 } 169 } else if (launchState.launchedFromHome) { 170 // Move the task view off screen (below) so we can animate it in 171 mTmpTransform.rect.offset(0, offscreenYOffset); 172 mTmpTransform.alpha = 0f; 173 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE); 174 } else if (launchState.launchedViaDockGesture) { 175 int offset = isLandscape 176 ? launchedWhileDockingOffset 177 : (int) (offscreenYOffset * 0.9f); 178 mTmpTransform.rect.offset(0, offset); 179 mTmpTransform.alpha = 0f; 180 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE); 181 } 182 } 183 } 184 185 /** 186 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places 187 * depending on how Recents was triggered. 188 */ 189 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { 190 RecentsConfiguration config = Recents.getConfiguration(); 191 RecentsActivityLaunchState launchState = config.getLaunchState(); 192 Resources res = mStackView.getResources(); 193 Resources appRes = mStackView.getContext().getApplicationContext().getResources(); 194 195 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 196 TaskStackViewScroller stackScroller = mStackView.getScroller(); 197 TaskStack stack = mStackView.getStack(); 198 Task launchTargetTask = stack.getLaunchTarget(); 199 200 // Break early if there are no tasks 201 if (stack.getTaskCount() == 0) { 202 return; 203 } 204 205 int taskViewEnterFromAppDuration = res.getInteger( 206 R.integer.recents_task_enter_from_app_duration); 207 int taskViewEnterFromAffiliatedAppDuration = res.getInteger( 208 R.integer.recents_task_enter_from_affiliated_app_duration); 209 int dockGestureAnimDuration = appRes.getInteger( 210 R.integer.long_press_dock_anim_duration); 211 212 // Create enter animations for each of the views from front to back 213 List<TaskView> taskViews = mStackView.getTaskViews(); 214 int taskViewCount = taskViews.size(); 215 for (int i = taskViewCount - 1; i >= 0; i--) { 216 int taskIndexFromFront = taskViewCount - i - 1; 217 int taskIndexFromBack = i; 218 final TaskView tv = taskViews.get(i); 219 Task task = tv.getTask(); 220 boolean currentTaskOccludesLaunchTarget = false; 221 if (launchTargetTask != null) { 222 currentTaskOccludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, 223 launchTargetTask); 224 } 225 226 // Get the current transform for the task, which will be updated to the final transform 227 // to animate to depending on how recents was invoked 228 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 229 null); 230 231 if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) { 232 if (task.isLaunchTarget) { 233 tv.onStartLaunchTargetEnterAnimation(mTmpTransform, 234 taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, 235 postAnimationTrigger); 236 } else { 237 // Animate the task up if it was occluding the launch target 238 if (currentTaskOccludesLaunchTarget) { 239 AnimationProps taskAnimation = new AnimationProps( 240 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, 241 new AnimatorListenerAdapter() { 242 @Override 243 public void onAnimationEnd(Animator animation) { 244 postAnimationTrigger.decrement(); 245 tv.setClipViewInStack(true); 246 } 247 }); 248 postAnimationTrigger.increment(); 249 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 250 } 251 } 252 253 } else if (launchState.launchedFromHome) { 254 // Animate the tasks up 255 AnimationProps taskAnimation = new AnimationProps() 256 .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS) 257 .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION) 258 .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION - 259 (taskIndexFromFront * FRAME_OFFSET_MS)) 260 .setInterpolator(AnimationProps.BOUNDS, 261 ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR) 262 .setInterpolator(AnimationProps.ALPHA, 263 ENTER_FROM_HOME_ALPHA_INTERPOLATOR) 264 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 265 postAnimationTrigger.increment(); 266 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 267 if (i == taskViewCount - 1) { 268 tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled); 269 } 270 } else if (launchState.launchedViaDockGesture) { 271 // Animate the tasks up 272 AnimationProps taskAnimation = new AnimationProps() 273 .setDuration(AnimationProps.BOUNDS, (int) (dockGestureAnimDuration + 274 (taskIndexFromBack * 2f * FRAME_OFFSET_MS))) 275 .setInterpolator(AnimationProps.BOUNDS, 276 ENTER_WHILE_DOCKING_INTERPOLATOR) 277 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 278 postAnimationTrigger.increment(); 279 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 280 } 281 } 282 } 283 284 /** 285 * Starts an in-app animation to hide all the task views so that we can transition back home. 286 */ 287 public void startExitToHomeAnimation(boolean animated, 288 ReferenceCountedTrigger postAnimationTrigger) { 289 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 290 TaskStack stack = mStackView.getStack(); 291 292 // Break early if there are no tasks 293 if (stack.getTaskCount() == 0) { 294 return; 295 } 296 297 int offscreenYOffset = stackLayout.mStackRect.height(); 298 299 // Create the animations for each of the tasks 300 List<TaskView> taskViews = mStackView.getTaskViews(); 301 int taskViewCount = taskViews.size(); 302 for (int i = 0; i < taskViewCount; i++) { 303 int taskIndexFromFront = taskViewCount - i - 1; 304 TaskView tv = taskViews.get(i); 305 Task task = tv.getTask(); 306 307 // Animate the tasks down 308 AnimationProps taskAnimation; 309 if (animated) { 310 taskAnimation = new AnimationProps() 311 .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS) 312 .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION) 313 .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION + 314 (taskIndexFromFront * FRAME_OFFSET_MS)) 315 .setInterpolator(AnimationProps.BOUNDS, 316 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR) 317 .setInterpolator(AnimationProps.ALPHA, 318 EXIT_TO_HOME_ALPHA_INTERPOLATOR) 319 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 320 postAnimationTrigger.increment(); 321 } else { 322 taskAnimation = AnimationProps.IMMEDIATE; 323 } 324 325 mTmpTransform.fillIn(tv); 326 mTmpTransform.alpha = 0f; 327 mTmpTransform.rect.offset(0, offscreenYOffset); 328 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 329 } 330 } 331 332 /** 333 * Starts the animation for the launching task view, hiding any tasks that might occlude the 334 * window transition for the launching task. 335 */ 336 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, 337 final ReferenceCountedTrigger postAnimationTrigger) { 338 Resources res = mStackView.getResources(); 339 340 int taskViewExitToAppDuration = res.getInteger( 341 R.integer.recents_task_exit_to_app_duration); 342 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 343 R.dimen.recents_task_stack_animation_affiliate_enter_offset); 344 345 Task launchingTask = launchingTaskView.getTask(); 346 List<TaskView> taskViews = mStackView.getTaskViews(); 347 int taskViewCount = taskViews.size(); 348 for (int i = 0; i < taskViewCount; i++) { 349 TaskView tv = taskViews.get(i); 350 Task task = tv.getTask(); 351 boolean currentTaskOccludesLaunchTarget = (launchingTask != null && 352 launchingTask.group.isTaskAboveTask(task, launchingTask)); 353 354 if (tv == launchingTaskView) { 355 tv.setClipViewInStack(false); 356 postAnimationTrigger.addLastDecrementRunnable(new Runnable() { 357 @Override 358 public void run() { 359 tv.setClipViewInStack(true); 360 } 361 }); 362 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, 363 screenPinningRequested, postAnimationTrigger); 364 } else if (currentTaskOccludesLaunchTarget) { 365 // Animate this task out of view 366 AnimationProps taskAnimation = new AnimationProps( 367 taskViewExitToAppDuration, Interpolators.ALPHA_OUT, 368 postAnimationTrigger.decrementOnAnimationEnd()); 369 postAnimationTrigger.increment(); 370 371 mTmpTransform.fillIn(tv); 372 mTmpTransform.alpha = 0f; 373 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 374 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 375 } 376 } 377 } 378 379 /** 380 * Starts the delete animation for the specified {@link TaskView}. 381 */ 382 public void startDeleteTaskAnimation(final TaskView deleteTaskView, 383 final ReferenceCountedTrigger postAnimationTrigger) { 384 Resources res = mStackView.getResources(); 385 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 386 387 int taskViewRemoveAnimDuration = res.getInteger( 388 R.integer.recents_animate_task_view_remove_duration); 389 int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left; 390 391 // Disabling clipping with the stack while the view is animating away, this will get 392 // restored when the task is next picked up from the view pool 393 deleteTaskView.setClipViewInStack(false); 394 395 // Compose the new animation and transform and star the animation 396 AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration, 397 Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { 398 @Override 399 public void onAnimationEnd(Animator animation) { 400 postAnimationTrigger.decrement(); 401 } 402 }); 403 postAnimationTrigger.increment(); 404 405 mTmpTransform.fillIn(deleteTaskView); 406 mTmpTransform.alpha = 0f; 407 mTmpTransform.rect.offset(offscreenXOffset, 0); 408 mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); 409 } 410 411 /** 412 * Starts the delete animation for all the {@link TaskView}s. 413 */ 414 public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, 415 final ReferenceCountedTrigger postAnimationTrigger) { 416 Resources res = mStackView.getResources(); 417 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 418 419 int taskViewRemoveAnimDuration = res.getInteger( 420 R.integer.recents_animate_task_views_remove_all_duration); 421 int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left; 422 423 int taskViewCount = taskViews.size(); 424 int startDelayMax = 125; 425 426 for (int i = taskViewCount - 1; i >= 0; i--) { 427 TaskView tv = taskViews.get(i); 428 int indexFromFront = taskViewCount - i - 1; 429 float x = Interpolators.ACCELERATE.getInterpolation((float) indexFromFront / 430 taskViewCount); 431 int startDelay = (int) Utilities.mapRange(x, 0, startDelayMax); 432 433 // Disabling clipping with the stack while the view is animating away 434 tv.setClipViewInStack(false); 435 436 // Compose the new animation and transform and star the animation 437 AnimationProps taskAnimation = new AnimationProps(startDelay, 438 taskViewRemoveAnimDuration, Interpolators.FAST_OUT_LINEAR_IN, 439 new AnimatorListenerAdapter() { 440 @Override 441 public void onAnimationEnd(Animator animation) { 442 postAnimationTrigger.decrement(); 443 444 // Re-enable clipping with the stack (we will reuse this view) 445 tv.setClipViewInStack(true); 446 } 447 }); 448 postAnimationTrigger.increment(); 449 450 mTmpTransform.fillIn(tv); 451 mTmpTransform.rect.offset(offscreenXOffset, 0); 452 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 453 } 454 } 455 456 /** 457 * Starts the animation to focus the next {@link TaskView} when paging through recents. 458 * 459 * @return whether or not this will trigger a scroll in the stack 460 */ 461 public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask, 462 boolean requestViewFocus) { 463 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 464 TaskStackViewScroller stackScroller = mStackView.getScroller(); 465 TaskStack stack = mStackView.getStack(); 466 467 final float curScroll = stackScroller.getStackScroll(); 468 final float newScroll = stackScroller.getBoundedStackScroll( 469 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