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