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