diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index b316edd737..c80e531e63 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -27,4 +27,5 @@ 18dp 10dp + -80dp \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 00e4a9d5a8..5af09f7fd9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -57,6 +57,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -151,8 +152,21 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override public AnimatorPlaybackController createActivityAnimationToHome() { + // Return an empty APC here since we have an non-user controlled animation to home. long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); - return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy); + AnimatorSet as = new AnimatorSet(); + as.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + activity.getStateManager().goToState(NORMAL, false); + } + }); + return AnimatorPlaybackController.wrap(as, accuracy); + } + + @Override + public void playAtomicAnimation(float velocity) { + new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start(); } }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index ca966c8fdb..187e5317f8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -1057,6 +1057,7 @@ public class WindowTransformSwipeHandler setStateOnUiThread(target.endState); } }); + homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); windowAnim.start(velocityPxPerMs); mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); mLauncherTransitionController = null; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java new file mode 100644 index 0000000000..93b6e4ba5d --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 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.util; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.anim.SpringObjectAnimator; + +import java.util.ArrayList; +import java.util.List; + +import static com.android.launcher3.anim.Interpolators.LINEAR; + +/** + * Creates an animation where all the workspace items are moved into their final location, + * staggered row by row from the bottom up. + * This is used in conjunction with the swipe up to home animation. + */ +public class StaggeredWorkspaceAnim { + + private static final int APP_CLOSE_ROW_START_DELAY_MS = 16; + private static final int ALPHA_DURATION_MS = 200; + + private static final float MAX_VELOCITY_PX_PER_S = 22f; + + private static final float DAMPING_RATIO = + (SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY + SpringForce.DAMPING_RATIO_LOW_BOUNCY) / 2f; + private static final float STIFFNESS = SpringForce.STIFFNESS_LOW; + + private final float mVelocity; + private final float mSpringTransY; + private final View mViewToIgnore; + + private final List mAnimators = new ArrayList<>(); + + /** + * @param floatingViewOriginalView The FloatingIconView's original view. + */ + public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, + float velocity) { + mVelocity = velocity; + // We ignore this view since it's visibility and position is controlled by + // the FloatingIconView. + mViewToIgnore = floatingViewOriginalView; + + // Scale the translationY based on the initial velocity to better sync the workspace items + // with the floating view. + float transFactor = 0.1f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S; + mSpringTransY = transFactor * launcher.getResources() + .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);; + + DeviceProfile grid = launcher.getDeviceProfile(); + ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace() + .getChildAt(launcher.getWorkspace().getCurrentPage())) + .getShortcutsAndWidgets(); + + // Hotseat and QSB takes up two additional rows. + int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2); + + // Set up springs on workspace items. + for (int i = currentPage.getChildCount() - 1; i >= 0; i--) { + View child = currentPage.getChildAt(i); + CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); + addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows); + } + + // Set up springs for the hotseat and qsb. + if (grid.isVerticalBarLayout()) { + ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0); + for (int i = hotseat.getChildCount() - 1; i >= 0; i--) { + View child = hotseat.getChildAt(i); + CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); + addStaggeredAnimationForView(child, lp.cellY + 1, totalRows); + } + } else { + View hotseat = launcher.getHotseat().getChildAt(0); + addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows); + + View qsb = launcher.findViewById(R.id.search_container_all_apps); + addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows); + } + } + + /** + * Starts the animation. + */ + public void start() { + for (Animator a : mAnimators) { + if (a instanceof SpringObjectAnimator) { + ((SpringObjectAnimator) a).startSpring(1f, mVelocity, null); + } else { + a.start(); + } + } + } + + /** + * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row. + * + * @param v A view on the workspace. + * @param row The bottom-most row that contains the view. + * @param totalRows Total number of rows. + */ + private void addStaggeredAnimationForView(View v, int row, int totalRows) { + if (v == mViewToIgnore) { + return; + } + + // Invert the rows, because we stagger starting from the bottom of the screen. + int invertedRow = totalRows - row; + // Add 1 to the inverted row so that the bottom most row has a start delay. + long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS); + + v.setTranslationY(mSpringTransY); + SpringObjectAnimator springTransY = new SpringObjectAnimator<>( + new ViewProgressProperty(v, View.TRANSLATION_Y), "staggeredSpringTransY", 1f, + DAMPING_RATIO, STIFFNESS, mSpringTransY, 0); + springTransY.setStartDelay(startDelay); + mAnimators.add(springTransY); + + v.setAlpha(0); + ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); + alpha.setInterpolator(LINEAR); + alpha.setDuration(ALPHA_DURATION_MS); + alpha.setStartDelay(startDelay); + mAnimators.add(alpha); + } +} diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index b0acd9b1b6..279a946195 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -143,5 +143,9 @@ public interface ActivityControlHelper { @NonNull RectF getWindowTargetRect(); @NonNull AnimatorPlaybackController createActivityAnimationToHome(); + + default void playAtomicAnimation(float velocity) { + // No-op + } } } diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java index f74590bba3..b1395af89f 100644 --- a/src/com/android/launcher3/anim/SpringObjectAnimator.java +++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java @@ -22,6 +22,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.util.Property; @@ -139,7 +141,7 @@ public class SpringObjectAnimator extends ValueAnim /** * Initializes and sets up the spring to take over controlling the object. */ - void startSpring(float end, float velocity, OnAnimationEndListener endListener) { + public void startSpring(float end, float velocity, OnAnimationEndListener endListener) { // Cancel the spring so we can set new start velocity and final position. We need to remove // the listener since the spring is not actually ending. mSpring.removeEndListener(endListener); @@ -149,7 +151,13 @@ public class SpringObjectAnimator extends ValueAnim mProperty.switchToSpring(); mSpring.setStartVelocity(velocity); - mSpring.animateToFinalPosition(end == 0 ? mValues[0] : mValues[1]); + + float startValue = end == 0 ? mValues[1] : mValues[0]; + float endValue = end == 0 ? mValues[0] : mValues[1]; + mSpring.setStartValue(startValue); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + mSpring.animateToFinalPosition(endValue); + }, getStartDelay()); } @Override