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

Commit f5bcd887 authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Refactoring filter animation logic."

parents 0f7ed1f4 11ca76a5
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;