TaskStackAnimationHelper.java revision e693aafe0511c2a7ffc571b22abeefba44046225
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 private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR = 75 new PathInterpolator(0, 0, 0, 1f); 76 private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = 77 new PathInterpolator(0, 0, 0.2f, 1f); 78 79 public static final int EXIT_TO_HOME_ALPHA_DURATION = 100; 80 public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150; 81 private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR = 82 new PathInterpolator(0.8f, 0, 0.6f, 1f); 83 private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR = 84 new PathInterpolator(0.4f, 0, 1f, 1f); 85 86 private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR = 87 new PathInterpolator(0.4f, 0, 0, 1f); 88 private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR = 89 new PathInterpolator(0, 0, 0, 1f); 90 private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR = 91 new PathInterpolator(0.4f, 0, 0.2f, 1f); 92 93 private TaskStackView mStackView; 94 95 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 96 private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>(); 97 private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>(); 98 99 public TaskStackAnimationHelper(Context context, TaskStackView stackView) { 100 mStackView = stackView; 101 } 102 103 /** 104 * Prepares the stack views and puts them in their initial animation state while visible, before 105 * the in-app enter animations start (after the window-transition completes). 106 */ 107 public void prepareForEnterAnimation() { 108 RecentsConfiguration config = Recents.getConfiguration(); 109 RecentsActivityLaunchState launchState = config.getLaunchState(); 110 Resources res = mStackView.getResources(); 111 112 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 113 TaskStackViewScroller stackScroller = mStackView.getScroller(); 114 TaskStack stack = mStackView.getStack(); 115 Task launchTargetTask = stack.getLaunchTarget(); 116 117 // Break early if there are no tasks 118 if (stack.getTaskCount() == 0) { 119 return; 120 } 121 122 int offscreenYOffset = stackLayout.mStackRect.height(); 123 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 124 R.dimen.recents_task_view_affiliate_group_enter_offset); 125 126 // Prepare each of the task views for their enter animation from front to back 127 List<TaskView> taskViews = mStackView.getTaskViews(); 128 for (int i = taskViews.size() - 1; i >= 0; i--) { 129 TaskView tv = taskViews.get(i); 130 Task task = tv.getTask(); 131 boolean currentTaskOccludesLaunchTarget = (launchTargetTask != null && 132 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask)); 133 boolean hideTask = (launchTargetTask != null && 134 launchTargetTask.isFreeformTask() && task.isFreeformTask()); 135 136 // Get the current transform for the task, which will be used to position it offscreen 137 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 138 null); 139 140 if (hideTask) { 141 tv.setVisibility(View.INVISIBLE); 142 } else if (launchState.launchedHasConfigurationChanged) { 143 // Just load the views as-is 144 } else if (launchState.launchedFromApp) { 145 if (task.isLaunchTarget) { 146 tv.onPrepareLaunchTargetForEnterAnimation(); 147 } else if (currentTaskOccludesLaunchTarget) { 148 // Move the task view slightly lower so we can animate it in 149 RectF bounds = new RectF(mTmpTransform.rect); 150 bounds.offset(0, taskViewAffiliateGroupEnterOffset); 151 tv.setClipViewInStack(false); 152 tv.setAlpha(0f); 153 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, 154 (int) bounds.right, (int) bounds.bottom); 155 } 156 } else if (launchState.launchedFromHome) { 157 // Move the task view off screen (below) so we can animate it in 158 RectF bounds = new RectF(mTmpTransform.rect); 159 bounds.offset(0, offscreenYOffset); 160 tv.setLeftTopRightBottom((int) bounds.left, (int) bounds.top, (int) bounds.right, 161 (int) bounds.bottom); 162 } 163 } 164 } 165 166 /** 167 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places 168 * depending on how Recents was triggered. 169 */ 170 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) { 171 RecentsConfiguration config = Recents.getConfiguration(); 172 RecentsActivityLaunchState launchState = config.getLaunchState(); 173 Resources res = mStackView.getResources(); 174 175 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 176 TaskStackViewScroller stackScroller = mStackView.getScroller(); 177 TaskStack stack = mStackView.getStack(); 178 Task launchTargetTask = stack.getLaunchTarget(); 179 180 // Break early if there are no tasks 181 if (stack.getTaskCount() == 0) { 182 return; 183 } 184 185 int taskViewEnterFromAppDuration = res.getInteger( 186 R.integer.recents_task_enter_from_app_duration); 187 int taskViewEnterFromAffiliatedAppDuration = res.getInteger( 188 R.integer.recents_task_enter_from_affiliated_app_duration); 189 190 // Create enter animations for each of the views from front to back 191 List<TaskView> taskViews = mStackView.getTaskViews(); 192 int taskViewCount = taskViews.size(); 193 for (int i = taskViewCount - 1; i >= 0; i--) { 194 int taskIndexFromFront = taskViewCount - i - 1; 195 final TaskView tv = taskViews.get(i); 196 Task task = tv.getTask(); 197 boolean currentTaskOccludesLaunchTarget = false; 198 if (launchTargetTask != null) { 199 currentTaskOccludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, 200 launchTargetTask); 201 } 202 203 // Get the current transform for the task, which will be updated to the final transform 204 // to animate to depending on how recents was invoked 205 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 206 null); 207 208 if (launchState.launchedFromApp) { 209 if (task.isLaunchTarget) { 210 tv.onStartLaunchTargetEnterAnimation(mTmpTransform, 211 taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled, 212 postAnimationTrigger); 213 } else { 214 // Animate the task up if it was occluding the launch target 215 if (currentTaskOccludesLaunchTarget) { 216 AnimationProps taskAnimation = new AnimationProps( 217 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, 218 new AnimatorListenerAdapter() { 219 @Override 220 public void onAnimationEnd(Animator animation) { 221 postAnimationTrigger.decrement(); 222 tv.setClipViewInStack(false); 223 } 224 }); 225 postAnimationTrigger.increment(); 226 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 227 } 228 } 229 230 } else if (launchState.launchedFromHome) { 231 // Animate the tasks up 232 AnimationProps taskAnimation = new AnimationProps() 233 .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS) 234 .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION) 235 .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION - 236 (taskIndexFromFront * FRAME_OFFSET_MS)) 237 .setInterpolator(AnimationProps.BOUNDS, 238 ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR) 239 .setInterpolator(AnimationProps.ALPHA, 240 ENTER_FROM_HOME_ALPHA_INTERPOLATOR) 241 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 242 postAnimationTrigger.increment(); 243 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 244 } 245 } 246 } 247 248 /** 249 * Starts an in-app animation to hide all the task views so that we can transition back home. 250 */ 251 public void startExitToHomeAnimation(boolean animated, 252 ReferenceCountedTrigger postAnimationTrigger) { 253 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 254 TaskStackViewScroller stackScroller = mStackView.getScroller(); 255 TaskStack stack = mStackView.getStack(); 256 257 // Break early if there are no tasks 258 if (stack.getTaskCount() == 0) { 259 return; 260 } 261 262 int offscreenYOffset = stackLayout.mStackRect.height(); 263 264 // Create the animations for each of the tasks 265 List<TaskView> taskViews = mStackView.getTaskViews(); 266 int taskViewCount = taskViews.size(); 267 for (int i = 0; i < taskViewCount; i++) { 268 int taskIndexFromFront = taskViewCount - i - 1; 269 TaskView tv = taskViews.get(i); 270 Task task = tv.getTask(); 271 272 // Animate the tasks down 273 AnimationProps taskAnimation; 274 if (animated) { 275 taskAnimation = new AnimationProps() 276 .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS) 277 .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION) 278 .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION + 279 (taskIndexFromFront * FRAME_OFFSET_MS)) 280 .setInterpolator(AnimationProps.BOUNDS, 281 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR) 282 .setInterpolator(AnimationProps.ALPHA, 283 EXIT_TO_HOME_ALPHA_INTERPOLATOR) 284 .setListener(postAnimationTrigger.decrementOnAnimationEnd()); 285 postAnimationTrigger.increment(); 286 } else { 287 taskAnimation = AnimationProps.IMMEDIATE; 288 } 289 290 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 291 null); 292 mTmpTransform.rect.offset(0, offscreenYOffset); 293 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 294 } 295 } 296 297 /** 298 * Starts the animation for the launching task view, hiding any tasks that might occlude the 299 * window transition for the launching task. 300 */ 301 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, 302 final ReferenceCountedTrigger postAnimationTrigger) { 303 Resources res = mStackView.getResources(); 304 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 305 TaskStackViewScroller stackScroller = mStackView.getScroller(); 306 307 int taskViewExitToAppDuration = res.getInteger( 308 R.integer.recents_task_exit_to_app_duration); 309 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( 310 R.dimen.recents_task_view_affiliate_group_enter_offset); 311 312 Task launchingTask = launchingTaskView.getTask(); 313 List<TaskView> taskViews = mStackView.getTaskViews(); 314 int taskViewCount = taskViews.size(); 315 for (int i = 0; i < taskViewCount; i++) { 316 TaskView tv = taskViews.get(i); 317 Task task = tv.getTask(); 318 boolean currentTaskOccludesLaunchTarget = (launchingTask != null && 319 launchingTask.group.isTaskAboveTask(task, launchingTask)); 320 321 if (tv == launchingTaskView) { 322 tv.setClipViewInStack(false); 323 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration, 324 screenPinningRequested, postAnimationTrigger); 325 } else if (currentTaskOccludesLaunchTarget) { 326 // Animate this task out of view 327 AnimationProps taskAnimation = new AnimationProps( 328 taskViewExitToAppDuration, Interpolators.ALPHA_OUT, 329 postAnimationTrigger.decrementOnAnimationEnd()); 330 postAnimationTrigger.increment(); 331 332 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 333 null); 334 mTmpTransform.alpha = 0f; 335 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset); 336 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 337 } 338 } 339 } 340 341 /** 342 * Starts the delete animation for the specified {@link TaskView}. 343 */ 344 public void startDeleteTaskAnimation(Task deleteTask, final TaskView deleteTaskView, 345 final ReferenceCountedTrigger postAnimationTrigger) { 346 Resources res = mStackView.getResources(); 347 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 348 TaskStackViewScroller stackScroller = mStackView.getScroller(); 349 350 int taskViewRemoveAnimDuration = res.getInteger( 351 R.integer.recents_animate_task_view_remove_duration); 352 int taskViewRemoveAnimTranslationXPx = res.getDimensionPixelSize( 353 R.dimen.recents_task_view_remove_anim_translation_x); 354 355 // Disabling clipping with the stack while the view is animating away 356 deleteTaskView.setClipViewInStack(false); 357 358 // Compose the new animation and transform and star the animation 359 AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration, 360 Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { 361 @Override 362 public void onAnimationEnd(Animator animation) { 363 postAnimationTrigger.decrement(); 364 365 // Re-enable clipping with the stack (we will reuse this view) 366 deleteTaskView.setClipViewInStack(true); 367 } 368 }); 369 postAnimationTrigger.increment(); 370 371 stackLayout.getStackTransform(deleteTask, stackScroller.getStackScroll(), mTmpTransform, 372 null); 373 mTmpTransform.alpha = 0f; 374 mTmpTransform.rect.offset(taskViewRemoveAnimTranslationXPx, 0); 375 mStackView.updateTaskViewToTransform(deleteTaskView, mTmpTransform, taskAnimation); 376 } 377 378 /** 379 * Starts the animation to hide the {@link TaskView}s when the history is shown. 380 */ 381 public void startShowHistoryAnimation(ReferenceCountedTrigger postAnimationTrigger) { 382 Resources res = mStackView.getResources(); 383 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 384 TaskStackViewScroller stackScroller = mStackView.getScroller(); 385 386 int offscreenY = stackLayout.mStackRect.bottom; 387 int historyTransitionDuration = res.getInteger( 388 R.integer.recents_history_transition_duration); 389 int startDelayIncr = 16; 390 391 List<TaskView> taskViews = mStackView.getTaskViews(); 392 int taskViewCount = taskViews.size(); 393 for (int i = taskViewCount - 1; i >= 0; i--) { 394 TaskView tv = taskViews.get(i); 395 Task task = tv.getTask(); 396 AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, 397 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN, 398 postAnimationTrigger.decrementOnAnimationEnd()); 399 postAnimationTrigger.increment(); 400 401 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, 402 null); 403 mTmpTransform.alpha = 0f; 404 mTmpTransform.rect.offsetTo(mTmpTransform.rect.left, offscreenY); 405 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 406 } 407 } 408 409 /** 410 * Starts the animation to show the {@link TaskView}s when the history is hidden. 411 */ 412 public void startHideHistoryAnimation() { 413 Resources res = mStackView.getResources(); 414 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 415 TaskStackViewScroller stackScroller = mStackView.getScroller(); 416 417 int historyTransitionDuration = res.getInteger( 418 R.integer.recents_history_transition_duration); 419 int startDelayIncr = 16; 420 421 List<TaskView> taskViews = mStackView.getTaskViews(); 422 int taskViewCount = taskViews.size(); 423 for (int i = taskViewCount - 1; i >= 0; i--) { 424 TaskView tv = taskViews.get(i); 425 AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, 426 historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN); 427 stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(), 428 mTmpTransform, null); 429 mTmpTransform.alpha = 1f; 430 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); 431 } 432 } 433 434 /** 435 * Starts the animation to focus the next {@link TaskView} when paging through recents. 436 * 437 * @return whether or not this will trigger a scroll in the stack 438 */ 439 public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask, 440 boolean requestViewFocus) { 441 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); 442 TaskStackViewScroller stackScroller = mStackView.getScroller(); 443 TaskStack stack = mStackView.getStack(); 444 445 final float curScroll = stackScroller.getStackScroll(); 446 final float newScroll = 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.getStackTasks(); 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 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 = Math.max(100, 100 + ((i - 1) * 50)); 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