Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 11ca76a5 authored by Winson Chung's avatar Winson Chung
Browse files

Refactoring filter animation logic.

Change-Id: Ic3b57f540da8fa0d2703eef34c43ed9e54f85021
parent f873e785
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -24,9 +24,6 @@ public class BakedBezierInterpolator implements TimeInterpolator {
     * P3 (1.0, 1.0)
     *
     * Values sampled with x at regular intervals between 0 and 1.
     *
     * These values were generated using:
     *   ./scripts/bezier_interpolator_values_gen.py 0.4 0.2
     */
    private static final float[] VALUES = new float[] {
        0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
+1 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ public class RecentsConfiguration {
                res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
    }

    /** Updates the system insets */
    public void updateSystemInsets(Rect insets) {
        systemInsets.set(insets);
    }
+2 −2
Original line number Diff line number Diff line
@@ -134,9 +134,9 @@ public class TaskStack {
        /* Notifies when a task has been removed from the stack */
        public void onStackTaskRemoved(TaskStack stack, Task t);
        /** Notifies when the stack was filtered */
        public void onStackFiltered(TaskStack newStack, ArrayList<Task> curStack, Task t);
        public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
        /** Notifies when the stack was un-filtered */
        public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curStack);
        public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
    }

    Context mContext;
+152 −232
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -45,6 +46,7 @@ import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;

import java.util.ArrayList;
import java.util.HashMap;


/* The visual representation of a task stack view */
@@ -76,9 +78,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
    OverScroller mScroller;
    ObjectAnimator mScrollAnimator;

    // Filtering
    AnimatorSet mFilterChildrenAnimator;

    // Optimizations
    int mHwLayersRefCount;
    int mStackViewsAnimationDuration;
@@ -645,163 +644,22 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
        requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
    }

    @Override
    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curStack,
                                Task filteredTask) {
        // NOTE: This code assumes that the current (unfiltered) stack is a superset of the new
        // (filtered) stack
        // XXX: Use HW Layers

        // Stash the scroll and filtered task for us to restore to when we unfilter
        mStashedScroll = getStackScroll();

        // Compute the transforms of the items in the current stack
        final ArrayList<TaskViewTransform> curTaskTransforms =
                getStackTransforms(curStack, mStashedScroll, null, true);

        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
        updateMinMaxScroll(false);
        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
        boundScrollRaw();

        // Compute the transforms of the items in the new stack after setting the new scroll
        final ArrayList<TaskViewTransform> taskTransforms =
                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);

        // Animate all of the existing views on screen either out of view (if they are not visible
        // in the new stack) or to their final positions in the new stack
        final ArrayList<TaskView> childrenToReturnToPool = new ArrayList<TaskView>();
        final ArrayList<Task> tasks = mStack.getTasks();
        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
        int childCount = getChildCount();
        int movement = 0;
        for (int i = 0; i < childCount; i++) {
            TaskView tv = (TaskView) getChildAt(i);
            Task task = tv.getTask();
            TaskViewTransform toTransform;
            int taskIndex = tasks.indexOf(task);

            boolean willBeInvisible = (taskIndex < 0) || !taskTransforms.get(taskIndex).visible;
            if (willBeInvisible) {
                // Compose a new transform that fades and slides the task out of view
                TaskViewTransform fromTransform = curTaskTransforms.get(curStack.indexOf(task));
                toTransform = new TaskViewTransform(fromTransform);
                tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);
                tv.prepareTaskTransformForFilterTaskHidden(toTransform);

                childrenToReturnToPool.add(tv);
            } else {
                toTransform = taskTransforms.get(taskIndex);

                // Use the movement of the visible views to calculate the duration of the animation
                movement = Math.max(movement,
                        Math.abs(toTransform.translationY - (int) tv.getTranslationY()));
            }
            childViewAnims.add(tv.getAnimatorToTaskTransform(toTransform));
        }

        // Cancel the previous animation
        if (mFilterChildrenAnimator != null) {
            mFilterChildrenAnimator.cancel();
            mFilterChildrenAnimator.removeAllListeners();
        }

        // Create a new animation for the existing children
        final RecentsConfiguration config = RecentsConfiguration.getInstance();
        mFilterChildrenAnimator = new AnimatorSet();
        mFilterChildrenAnimator.setDuration(
                Utilities.calculateTranslationAnimationDuration(movement,
                        config.filteringCurrentViewsMinAnimDuration));
        mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
        mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
            boolean isCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                isCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (isCancelled) return;

                // Return all the removed children to the view pool
                for (TaskView tv : childrenToReturnToPool) {
                    mViewPool.returnViewToPool(tv);
                }

                // For views that are not already visible, animate them in
                ArrayList<Animator> newViewsAnims = new ArrayList<Animator>();
                int taskCount = tasks.size();
                int movement = 0;
                int offset = 0;
                for (int i = 0; i < taskCount; i++) {
                    Task task = tasks.get(i);
                    TaskViewTransform toTransform = taskTransforms.get(i);
                    if (toTransform.visible) {
                        TaskViewTransform fromTransform =
                                curTaskTransforms.get(curStack.indexOf(task));
                        TaskView tv = getChildViewForTask(task);
                        if (tv == null) {
                            tv = mViewPool.pickUpViewFromPool(task, task);
                            // Compose a new transform that fades and slides the new task in
                            fromTransform = new TaskViewTransform(toTransform);
                            tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
                            tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);

                            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
                            anim.setStartDelay(offset *
                                    Constants.Values.TaskStackView.FilterStartDelay);
                            newViewsAnims.add(anim);

                            // Use the movement of the newly visible views to calculate the duration
                            // of the animation
                            movement = Math.max(movement, Math.abs(toTransform.translationY -
                                    fromTransform.translationY));
                            offset++;
                        }
                    }

                    // Animate the new views in
                    mFilterChildrenAnimator = new AnimatorSet();
                    mFilterChildrenAnimator.setDuration(
                            Utilities.calculateTranslationAnimationDuration(movement,
                                    config.filteringNewViewsMinAnimDuration));
                    mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
                    mFilterChildrenAnimator.playTogether(newViewsAnims);
                    mFilterChildrenAnimator.start();
                }
                invalidate();
            }
        });
        mFilterChildrenAnimator.playTogether(childViewAnims);
        mFilterChildrenAnimator.start();
    }

    @Override
    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curStack) {
        // Compute the transforms of the items in the current stack
        final int curScroll = getStackScroll();
        final ArrayList<TaskViewTransform> curTaskTransforms =
                getStackTransforms(curStack, curScroll, null, true);

        // Restore the stashed scroll
        updateMinMaxScroll(false);
        setStackScrollRaw(mStashedScroll);
        boundScrollRaw();

        // Compute the transforms of the items in the new stack after restoring the stashed scroll
        final ArrayList<TaskViewTransform> taskTransforms =
                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);

    /**
     * Creates the animations for all the children views that need to be removed or to move views
     * to their un/filtered position when we are un/filtering a stack, and returns the duration
     * for these animations.
     */
    int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
                        ArrayList<TaskViewTransform> curTaskTransforms,
                        ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
                        HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
                        ArrayList<TaskView> childrenToRemoveOut,
                        RecentsConfiguration config) {
        // Animate all of the existing views out of view (if they are not in the visible range in
        // the new stack) or to their final positions in the new stack
        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
        final ArrayList<Task> tasks = mStack.getTasks();
        ArrayList<Animator> childViewAnims = new ArrayList<Animator>();
        int childCount = getChildCount();
        int movement = 0;
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            TaskView tv = (TaskView) getChildAt(i);
            Task task = tv.getTask();
@@ -811,59 +669,37 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
            // If the view is no longer visible, then we should just animate it out
            boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
            if (willBeInvisible) {
                if (taskIndex < 0) {
                    toTransform = curTaskTransforms.get(curTasks.indexOf(task));
                } else {
                    toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
                }
                tv.prepareTaskTransformForFilterTaskVisible(toTransform);
                childrenToRemove.add(tv);
                childrenToRemoveOut.add(tv);
            } else {
                toTransform = taskTransforms.get(taskIndex);
                // Use the movement of the visible views to calculate the duration of the animation
                movement = Math.max(movement, Math.abs(toTransform.translationY -
                        (int) tv.getTranslationY()));
            }

            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
            childViewAnims.add(anim);
            childViewTransformsOut.put(tv, new Pair(0, toTransform));
        }

        // Cancel the previous animation
        if (mFilterChildrenAnimator != null) {
            mFilterChildrenAnimator.cancel();
            mFilterChildrenAnimator.removeAllListeners();
        return Utilities.calculateTranslationAnimationDuration(movement,
                config.filteringCurrentViewsMinAnimDuration);
    }

        // Create a new animation for the existing children
        final RecentsConfiguration config = RecentsConfiguration.getInstance();
        mFilterChildrenAnimator = new AnimatorSet();
        mFilterChildrenAnimator.setDuration(
                Utilities.calculateTranslationAnimationDuration(movement,
                        config.filteringCurrentViewsMinAnimDuration));
        mFilterChildrenAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
        mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
            boolean isCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                isCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (isCancelled) return;

                // Return all the removed children to the view pool
                for (TaskView tv : childrenToRemove) {
                    mViewPool.returnViewToPool(tv);
                }

                // Increment the hw layers ref count
                addHwLayersRefCount("unfilteredNewViews");

                // For views that are not already visible, animate them in
                ArrayList<Animator> newViewAnims = new ArrayList<Animator>();
                int taskCount = tasks.size();
                int movement = 0;
    /**
     * Creates the animations for all the children views that need to be animated in when we are
     * un/filtering a stack, and returns the duration for these animations.
     */
    int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
                         ArrayList<TaskViewTransform> taskTransforms,
                         HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut,
                         RecentsConfiguration config) {
        int offset = 0;
                for (int i = 0; i < taskCount; i++) {
        int movement = 0;
        int taskCount = tasks.size();
        for (int i = taskCount - 1; i >= 0; i--) {
            Task task = tasks.get(i);
            TaskViewTransform toTransform = taskTransforms.get(i);
            if (toTransform.visible) {
@@ -877,38 +713,122 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
                    tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
                    tv.updateViewPropertiesToTaskTransform(null, fromTransform, 0);

                            Animator anim = tv.getAnimatorToTaskTransform(toTransform);
                            anim.setStartDelay(offset *
                                    Constants.Values.TaskStackView.FilterStartDelay);
                            newViewAnims.add(anim);
                            // Use the movement of the newly visible views to calculate the duration
                            // of the animation
                    int startDelay = offset *
                            Constants.Values.TaskStackView.FilterStartDelay;
                    childViewTransformsOut.put(tv, new Pair(startDelay, toTransform));

                    // Use the movement of the new views to calculate the duration of the animation
                    movement = Math.max(movement,
                            Math.abs(toTransform.translationY - fromTransform.translationY));
                    offset++;
                }
            }
        }
        return Utilities.calculateTranslationAnimationDuration(movement,
                config.filteringNewViewsMinAnimDuration);
    }

                // Run the animation
                mFilterChildrenAnimator = new AnimatorSet();
                mFilterChildrenAnimator.setDuration(
                        Utilities.calculateTranslationAnimationDuration(movement,
                                config.filteringNewViewsMinAnimDuration));
                mFilterChildrenAnimator.playTogether(newViewAnims);
                mFilterChildrenAnimator.addListener(new AnimatorListenerAdapter() {
    /** Orchestrates the animations of the current child views and any new views. */
    void doFilteringAnimation(ArrayList<Task> curTasks,
                              ArrayList<TaskViewTransform> curTaskTransforms,
                              final ArrayList<Task> tasks,
                              final ArrayList<TaskViewTransform> taskTransforms) {
        final RecentsConfiguration config = RecentsConfiguration.getInstance();

        // Calculate the transforms to animate out all the existing views if they are not in the
        // new visible range (or to their final positions in the stack if they are)
        final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
        final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms =
                new HashMap<TaskView, Pair<Integer, TaskViewTransform>>();
        int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
                taskTransforms, childViewTransforms, childrenToRemove, config);

        // If all the current views are in the visible range of the new stack, then don't wait for
        // views to animate out and animate all the new views into their place
        final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
        if (unifyNewViewAnimation) {
            int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
                    childViewTransforms, config);
            duration = Math.max(duration, inDuration);
        }

        // Animate all the views to their final transforms
        for (final TaskView tv : childViewTransforms.keySet()) {
            Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
            tv.animate().cancel();
            tv.animate()
                    .setStartDelay(t.first)
                    .withEndAction(new Runnable() {
                        @Override
                    public void onAnimationEnd(Animator animation) {
                        // Decrement the hw layers ref count
                        decHwLayersRefCount("unfilteredNewViews");
                        public void run() {
                            childViewTransforms.remove(tv);
                            if (childViewTransforms.isEmpty()) {
                                // Return all the removed children to the view pool
                                for (TaskView tv : childrenToRemove) {
                                    mViewPool.returnViewToPool(tv);
                                }

                                if (!unifyNewViewAnimation) {
                                    // For views that are not already visible, animate them in
                                    childViewTransforms.clear();
                                    int duration = getEnterTransformsForFilterAnimation(tasks,
                                            taskTransforms, childViewTransforms, config);
                                    for (final TaskView tv : childViewTransforms.keySet()) {
                                        Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv);
                                        tv.animate().setStartDelay(t.first);
                                        tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
                                    }
                                }
                            }
                });
                mFilterChildrenAnimator.start();
                invalidate();
                        }
                    });
        mFilterChildrenAnimator.playTogether(childViewAnims);
        mFilterChildrenAnimator.start();
            tv.updateViewPropertiesToTaskTransform(null, t.second, duration);
        }
    }

    @Override
    public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
                                Task filteredTask) {
        // Stash the scroll and filtered task for us to restore to when we unfilter
        mStashedScroll = getStackScroll();

        // Calculate the current task transforms
        ArrayList<TaskViewTransform> curTaskTransforms =
                getStackTransforms(curTasks, getStackScroll(), null, true);

        // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
        updateMinMaxScroll(false);
        float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
        setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
        boundScrollRaw();

        // Compute the transforms of the items in the new stack after setting the new scroll
        final ArrayList<Task> tasks = mStack.getTasks();
        final ArrayList<TaskViewTransform> taskTransforms =
                getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);

        // Animate
        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
    }

    @Override
    public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
        // Calculate the current task transforms
        final ArrayList<TaskViewTransform> curTaskTransforms =
                getStackTransforms(curTasks, getStackScroll(), null, true);

        // Restore the stashed scroll
        updateMinMaxScroll(false);
        setStackScrollRaw(mStashedScroll);
        boundScrollRaw();

        // Compute the transforms of the items in the new stack after restoring the stashed scroll
        final ArrayList<Task> tasks = mStack.getTasks();
        final ArrayList<TaskViewTransform> taskTransforms =
                getStackTransforms(tasks, getStackScroll(), null, true);

        // Animate
        doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);

        // Clear the saved vars
        mStashedScroll = 0;
+8 −20
Original line number Diff line number Diff line
@@ -16,21 +16,13 @@

package com.android.systemui.recents.views;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.BakedBezierInterpolator;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.Utilities;
import com.android.systemui.recents.model.Task;
@@ -115,18 +107,6 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task.
        }
    }

    /** Returns an animator to animate this task to the specified transform */
    Animator getAnimatorToTaskTransform(TaskViewTransform toTransform) {
        AnimatorSet anims = new AnimatorSet();
        anims.playTogether(
                ObjectAnimator.ofFloat(this, "translationY", toTransform.translationY),
                ObjectAnimator.ofFloat(this, "scaleX", toTransform.scale),
                ObjectAnimator.ofFloat(this, "scaleY", toTransform.scale),
                ObjectAnimator.ofFloat(this, "alpha", toTransform.alpha)
        );
        return anims;
    }

    /** Resets this view's properties */
    void resetViewProperties() {
        setTranslationX(0f);
@@ -136,12 +116,20 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task.
        setAlpha(1f);
    }

    /**
     * When we are un/filtering, this method will set up the transform that we are animating to,
     * in order to hide the task.
     */
    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
        // Fade the view out and slide it away
        toTransform.alpha = 0f;
        toTransform.translationY += 200;
    }

    /**
     * When we are un/filtering, this method will setup the transform that we are animating from,
     * in order to show the task.
     */
    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
        // Fade the view in
        fromTransform.alpha = 0f;