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

Commit 20a13ff5 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Updating the swipe-to-dismiss animation

Swipe to dismiss animates the page translation so that the
final position at the endof swipe is same as the final position
on removing the task view.

Change-Id: I393acd5ae75cd94797bb4f1aa3dd3ec9017cbb47
parent f633ef5f
Loading
Loading
Loading
Loading
+17 −19
Original line number Diff line number Diff line
@@ -18,14 +18,11 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.util.Log;
import android.view.MotionEvent;
@@ -43,6 +40,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.PendingAnimation;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;

@@ -65,6 +63,7 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
    private final RecentsView mRecentsView;
    private final int[] mTempCords = new int[2];

    private PendingAnimation mPendingAnimation;
    private AnimatorPlaybackController mCurrentAnimation;
    private boolean mCurrentAnimationIsGoingUp;

@@ -178,6 +177,11 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
        if (mCurrentAnimation != null) {
            mCurrentAnimation.setPlayFraction(0);
        }
        if (mPendingAnimation != null) {
            mPendingAnimation.finish(false);
            mPendingAnimation = null;
        }

        mCurrentAnimationIsGoingUp = goingUp;
        float range = mLauncher.getAllAppsController().getShiftRange();
        long maxDuration = (long) (2 * range);
@@ -194,19 +198,11 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
            }
        } else {
            if (goingUp) {
                AnimatorSet anim = new AnimatorSet();
                ObjectAnimator translate = ObjectAnimator.ofFloat(
                        mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
                translate.setInterpolator(LINEAR);
                translate.setDuration(maxDuration);
                anim.play(translate);

                ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
                alpha.setInterpolator(DEACCEL_1_5);
                alpha.setDuration(maxDuration);
                anim.play(alpha);
                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
                mEndDisplacement = -mTaskBeingDragged.getBottom();
                mPendingAnimation = mRecentsView
                        .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
                mCurrentAnimation = AnimatorPlaybackController
                        .wrap(mPendingAnimation.anim, maxDuration);
                mEndDisplacement = -mTaskBeingDragged.getHeight();
            } else {
                AnimatorSet anim = new AnimatorSet();
                // TODO: Setup a zoom animation
@@ -292,15 +288,17 @@ public class OverviewSwipeController extends AnimatorListenerAdapter
    }

    private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
        if (mPendingAnimation != null) {
            mPendingAnimation.finish(wasSuccess);
            mPendingAnimation = null;
        }
        if (mTaskBeingDragged == null) {
            LauncherState state = wasSuccess ?
                    (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
            mLauncher.getStateManager().goToState(state, false);

        } else if (wasSuccess) {
            if (mCurrentAnimationIsGoingUp) {
                mRecentsView.onTaskDismissed(mTaskBeingDragged);
            } else {
            if (!mCurrentAnimationIsGoingUp) {
                mTaskBeingDragged.launchTask(false);
                mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
                        Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
+1 −4
Original line number Diff line number Diff line
@@ -57,10 +57,7 @@ public class RecentsViewStateController implements StateHandler {
        setVisibility(state.overviewUi);
        setTransitionProgress(state.overviewUi ? 1 : 0);
        if (state.overviewUi) {
            for (int i = 0; i < mRecentsView.getPageCount(); i++) {
                ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
            }
            mRecentsView.updateCurveProperties();
            mRecentsView.resetTaskVisuals();
        }
    }

+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.quickstep;

import android.animation.AnimatorSet;
import android.annotation.TargetApi;
import android.os.Build;

import java.util.ArrayList;
import java.util.function.Consumer;

/**
 * Utility class to keep track of a running animation.
 *
 * This class allows attaching end callbacks to an animation is intended to be used with
 * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
 * AnimationListeners are not properly dispatched.
 */
@TargetApi(Build.VERSION_CODES.O)
public class PendingAnimation {

    private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();

    public final AnimatorSet anim;

    public PendingAnimation(AnimatorSet anim) {
        this.anim = anim;
    }

    public void finish(boolean isSuccess) {
        for (Consumer<Boolean> listeners : mEndListeners) {
            listeners.accept(isSuccess);
        }
        mEndListeners.clear();
    }

    public void addEndListener(Consumer<Boolean> listener) {
        mEndListeners.add(listener);
    }
}
+105 −49
Original line number Diff line number Diff line
@@ -16,23 +16,32 @@

package com.android.quickstep.views;

import android.animation.LayoutTransition;
import android.animation.LayoutTransition.TransitionListener;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.LINEAR;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.PendingAnimation;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.RecentsModel;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
@@ -49,6 +58,7 @@ import java.util.ArrayList;
/**
 * A list of recent tasks.
 */
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity>
        extends PagedView implements OnSharedPreferenceChangeListener {

@@ -90,14 +100,14 @@ public abstract class RecentsView<T extends BaseActivity>

    private boolean mOverviewStateEnabled;
    private boolean mTaskStackListenerRegistered;
    private LayoutTransition mLayoutTransition;
    private Runnable mNextPageSwitchRunnable;

    private PendingAnimation mPendingAnimation;

    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
        enableFreeScroll(true);
        setupLayoutTransition();
        setClipToOutline(true);

        mFastFlingVelocity = getResources()
@@ -136,33 +146,6 @@ public abstract class RecentsView<T extends BaseActivity>
        return null;
    }

    private void setupLayoutTransition() {
        // We want to show layout transitions when pages are deleted, to close the gap.
        // TODO: We should this manually so we can control the animation (fill in the gap as the
        // dismissing task is being tracked, and also so we can update the visible task data during
        // the transition. For now, the workaround is to expand the visible tasks to load.
        mLayoutTransition = new LayoutTransition();
        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);

        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
        mLayoutTransition.addTransitionListener(new TransitionListener() {
            @Override
            public void startTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
                    View view, int i) {
                loadVisibleTaskData();
            }

            @Override
            public void endTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
                    View view, int i) {
                loadVisibleTaskData();
            }
        });
        setLayoutTransition(mLayoutTransition);
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
@@ -231,6 +214,10 @@ public abstract class RecentsView<T extends BaseActivity>
    }

    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
        if (mPendingAnimation != null) {
            mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
            return;
        }
        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
        if (stack == null) {
            removeAllViews();
@@ -243,7 +230,6 @@ public abstract class RecentsView<T extends BaseActivity>
        // necessary)
        final LayoutInflater inflater = LayoutInflater.from(getContext());
        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
        setLayoutTransition(null);

        final int requiredChildCount = tasks.size();
        for (int i = getChildCount(); i < requiredChildCount; i++) {
@@ -254,7 +240,6 @@ public abstract class RecentsView<T extends BaseActivity>
            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
            removeView(taskView);
        }
        setLayoutTransition(mLayoutTransition);

        // Unload existing visible task data
        unloadVisibleTaskData();
@@ -265,12 +250,8 @@ public abstract class RecentsView<T extends BaseActivity>
            final Task task = tasks.get(i);
            final TaskView taskView = (TaskView) getChildAt(pageIndex);
            taskView.bind(task);
            taskView.resetVisualProperties();
        }
        updateCurveProperties();

        // Update the set of visible task's data
        loadVisibleTaskData();
        resetTaskVisuals();
        applyIconScale(false /* animate */);

        if (oldChildCount != getChildCount()) {
@@ -278,6 +259,16 @@ public abstract class RecentsView<T extends BaseActivity>
        }
    }

    public void resetTaskVisuals() {
        for (int i = getChildCount() - 1; i >= 0; i--) {
            ((TaskView) getChildAt(i)).resetVisualProperties();
        }

        updateCurveProperties();
        // Update the set of visible task's data
        loadVisibleTaskData();
    }

    private void updateTaskStackListenerState() {
        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
                && getWindowVisibility() == VISIBLE;
@@ -375,7 +366,7 @@ public abstract class RecentsView<T extends BaseActivity>
        final int pageCount = getPageCount();
        for (int i = 0; i < pageCount; i++) {
            View page = getPageAt(i);
            int pageCenter = page.getLeft() + halfPageWidth;
            float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
            float distanceFromScreenCenter = screenCenter - pageCenter;
            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
            mScrollState.linearInterpolation = Math.min(1,
@@ -432,13 +423,6 @@ public abstract class RecentsView<T extends BaseActivity>
        mHasVisibleTaskData.clear();
    }

    public void onTaskDismissed(TaskView taskView) {
        ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
        removeView(taskView);
        if (getChildCount() == 0) {
            onAllTasksRemoved();
        }
    }

    protected abstract void onAllTasksRemoved();

@@ -470,11 +454,9 @@ public abstract class RecentsView<T extends BaseActivity>
        if (getChildCount() == 0) {
            needsReload = true;
            // Add an empty view for now
            setLayoutTransition(null);
            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
                    .inflate(R.layout.task, this, false);
            addView(taskView, 0);
            setLayoutTransition(mLayoutTransition);
        }
        mRunningTaskId = runningTaskId;
        setCurrentPage(0);
@@ -529,4 +511,78 @@ public abstract class RecentsView<T extends BaseActivity>
         */
        public float linearInterpolation;
    }

    public PendingAnimation createTaskDismissAnimation(TaskView taskView, long duration) {
        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
            throw new IllegalStateException("Another pending animation is still running");
        }
        AnimatorSet anim = new AnimatorSet();
        PendingAnimation pendingAnimation = new PendingAnimation(anim);

        int count = getChildCount();
        if (count == 0) {
            return pendingAnimation;
        }

        int[] oldScroll = new int[count];
        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);

        int[] newScroll = new int[count];
        getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);

        int maxScrollDiff = 0;
        int lastPage = mIsRtl ? 0 : count - 1;
        if (getChildAt(lastPage) == taskView) {
            if (count > 1) {
                int secondLastPage = mIsRtl ? 1 : count - 2;
                maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
            }
        }

        boolean needsCurveUpdates = false;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child == taskView) {
                addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
                addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
                        duration, LINEAR, anim);
            } else {
                int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
                if (scrollDiff != 0) {
                    addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
                            duration, ACCEL, anim);
                    needsCurveUpdates = true;
                }
            }
        }

        if (needsCurveUpdates) {
            ValueAnimator va = ValueAnimator.ofFloat(0, 1);
            va.addUpdateListener((a) -> updateCurveProperties());
            anim.play(va);
        }

        // Add a tiny bit of translation Z, so that it draws on top of other views
        taskView.setTranslationZ(0.1f);

        mPendingAnimation = pendingAnimation;
        mPendingAnimation.addEndListener((isSuccess) -> {
           if (isSuccess) {
               ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
               removeView(taskView);
               if (getChildCount() == 0) {
                   onAllTasksRemoved();
               }
           }
           resetTaskVisuals();
           mPendingAnimation = null;
        });
        return pendingAnimation;
    }

    private static void addAnim(ObjectAnimator anim, long duration,
            TimeInterpolator interpolator, AnimatorSet set) {
        anim.setDuration(duration).setInterpolator(interpolator);
        set.play(anim);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -154,6 +154,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
        setScaleY(1f);
        setTranslationX(0f);
        setTranslationY(0f);
        setTranslationZ(0);
        setAlpha(1f);
    }

Loading