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