diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 009ca27603..827eb7d0bb 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -693,7 +693,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener matrix.postTranslate(windowTransX0, windowTransY0); } - floatingView.update(floatingIconBounds, mIconAlpha.value, percent, 0f, + floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f, mWindowRadius.value * scale, true /* isOpening */); builder.withMatrix(matrix) .withWindowCrop(crop) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index dbb8272621..7e5b39cd52 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -1252,7 +1252,7 @@ public abstract class AbsSwipeUpHandler, HomeAnimationFactory homeAnimationFactory) { RectFSpringAnim anim = super.createWindowAnimationToHome(startProgress, homeAnimationFactory); - anim.addOnUpdateListener((r, p) -> { + anim.addOnUpdateListener((v, r, p) -> { updateSysUiFlags(Math.max(p, mCurrentShift.value)); }); anim.addAnimatorListener(new AnimationSuccessListener() { diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java index ec1cc4a648..f5698f7542 100644 --- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java @@ -48,6 +48,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; @@ -56,6 +57,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.fallback.FallbackRecentsView; import com.android.quickstep.fallback.RecentsState; +import com.android.quickstep.util.AppCloseConfig; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; @@ -298,7 +300,8 @@ public class FallbackSwipeHandler extends } @Override - public void update(RectF currentRect, float progress, float radius) { + public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress, + float radius) { if (mSurfaceControl != null) { currentRect.roundOut(mTempRect); Transaction t = new Transaction(); diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index 311ac83c00..5ecd385f13 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -15,26 +15,45 @@ */ package com.android.quickstep; +import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.Utilities.dpToPx; +import static com.android.launcher3.Utilities.mapToRange; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; +import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.RectF; import android.os.UserHandle; import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; +import com.android.launcher3.R; +import com.android.launcher3.Workspace; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.SpringAnimationBuilder; +import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.states.StateAnimationConfig; +import com.android.launcher3.util.DynamicResource; import com.android.launcher3.views.FloatingIconView; +import com.android.quickstep.util.AppCloseConfig; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.system.InputConsumerController; /** @@ -66,24 +85,41 @@ public class LauncherSwipeHandlerV2 extends } else { workspaceView = null; } - final RectF iconLocation = new RectF(); boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow(); - FloatingIconView floatingIconView = canUseWorkspaceView - ? FloatingIconView.getFloatingIconView(mActivity, workspaceView, - true /* hideOriginal */, iconLocation, false /* isOpening */) - : null; mActivity.getRootView().setForceHideBackArrow(true); mActivity.setHintUserWillBeActive(); if (canUseWorkspaceView) { + final ResourceProvider rp = DynamicResource.provider(mActivity); + final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp)); + float dpPerSecond = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp_per_s)); + final float launcherAlphaMax = + rp.getFloat(R.dimen.swipe_up_launcher_alpha_max_progress); + + RectF iconLocation = new RectF(); + FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView, + true /* hideOriginal */, iconLocation, false /* isOpening */); + // We want the window alpha to be 0 once this threshold is met, so that the // FolderIconView can be seen morphing into the icon shape. float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; homeAnimFactory = new LauncherHomeAnimationFactory() { + + // There is a delay in loading the icon, so we need to keep the window + // opaque until it is ready. + private boolean mIsFloatingIconReady = false; + + private @Nullable ValueAnimator mBounceBackAnimator; + @Override public RectF getWindowTargetRect() { + if (PROTOTYPE_APP_CLOSE.get()) { + // We want the target rect to be at this offset position, so that all + // launcher content can spring back upwards. + floatingIconView.setPositionOffsetY(transY); + } return iconLocation; } @@ -92,17 +128,92 @@ public class LauncherSwipeHandlerV2 extends anim.addAnimatorListener(floatingIconView); floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged); floatingIconView.setFastFinishRunnable(anim::end); + if (PROTOTYPE_APP_CLOSE.get()) { + mBounceBackAnimator = bounceBackToRestingPosition(); + // Use a spring to put drag layer translation back to 0. + anim.addAnimatorListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + floatingIconView.setPositionOffsetY(0); + mBounceBackAnimator.start(); + } + }); + + Workspace workspace = mActivity.getWorkspace(); + workspace.setPivotToScaleWithSelf(mActivity.getHotseat()); + } + } + + private ValueAnimator bounceBackToRestingPosition() { + DragLayer dl = mActivity.getDragLayer(); + Workspace workspace = mActivity.getWorkspace(); + Hotseat hotseat = mActivity.getHotseat(); + + final float startValue = transY; + final float endValue = 0; + // Ensures the velocity is always aligned with the direction. + float pixelPerSecond = Math.abs(dpPerSecond) + * Math.signum(endValue - transY); + + ValueAnimator springTransY = new SpringAnimationBuilder(dl.getContext()) + .setStiffness(rp.getFloat(R.dimen.swipe_up_trans_y_stiffness)) + .setDampingRatio(rp.getFloat(R.dimen.swipe_up_trans_y_damping)) + .setMinimumVisibleChange(1f) + .setStartValue(startValue) + .setEndValue(endValue) + .setStartVelocity(pixelPerSecond) + .build(dl, VIEW_TRANSLATE_Y); + springTransY.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dl.setTranslationY(0f); + dl.setAlpha(1f); + SCALE_PROPERTY.set(workspace, 1f); + SCALE_PROPERTY.set(hotseat, 1f); + } + }); + return springTransY; } @Override - public void update(RectF currentRect, float progress, float radius) { - floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold, - radius, false); + public boolean keepWindowOpaque() { + if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) { + mIsFloatingIconReady = true; + return false; + } + return true; + } + + @Override + public void update(@Nullable AppCloseConfig config, RectF currentRect, + float progress, float radius) { + int fgAlpha = 255; + if (config != null && PROTOTYPE_APP_CLOSE.get()) { + DragLayer dl = mActivity.getDragLayer(); + float translationY = config.getWorkspaceTransY(); + dl.setTranslationY(translationY); + + float alpha = mapToRange(progress, 0, launcherAlphaMax, 0, 1f, LINEAR); + dl.setAlpha(Math.min(alpha, 1f)); + + float scale = Math.min(1f, config.getWorkspaceScale()); + SCALE_PROPERTY.set(mActivity.getWorkspace(), scale); + SCALE_PROPERTY.set(mActivity.getHotseat(), scale); + SCALE_PROPERTY.set(mActivity.getAppsView(), scale); + + progress = config.getInterpolatedProgress(); + fgAlpha = config.getFgAlpha(); + } + floatingIconView.update(1f, fgAlpha, currentRect, progress, + windowAlphaThreshold, radius, false); } @Override public void onCancel() { floatingIconView.fastFinish(); + if (mBounceBackAnimator != null) { + mBounceBackAnimator.cancel(); + } } }; } else { diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index a8c09dc8fa..0f34a72ad4 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -17,6 +17,7 @@ package com.android.quickstep; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; import android.animation.Animator; import android.content.Context; @@ -26,6 +27,7 @@ import android.graphics.Rect; import android.graphics.RectF; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; @@ -35,7 +37,9 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.util.AnimatorControllerWithResistance; +import com.android.quickstep.util.AppCloseConfig; import com.android.quickstep.util.RectFSpringAnim; +import com.android.quickstep.util.RectFSpringAnim2; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; @@ -149,7 +153,10 @@ public abstract class SwipeUpAnimationLogic { public void setAnimation(RectFSpringAnim anim) { } - public void update(RectF currentRect, float progress, float radius) { } + public boolean keepWindowOpaque() { return false; } + + public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress, + float radius) { } public void onCancel() { } @@ -199,7 +206,14 @@ public abstract class SwipeUpAnimationLogic { homeToWindowPositionMap.invert(windowToHomePositionMap); windowToHomePositionMap.mapRect(startRect); - RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext); + RectFSpringAnim anim; + if (PROTOTYPE_APP_CLOSE.get()) { + anim = new RectFSpringAnim2(startRect, targetRect, mContext, + mTaskViewSimulator.getCurrentCornerRadius(), + cropRectF.width() / 2f); + } else { + anim = new RectFSpringAnim(startRect, targetRect, mContext); + } homeAnimationFactory.setAnimation(anim); SpringAnimationRunner runner = new SpringAnimationRunner( @@ -259,18 +273,26 @@ public abstract class SwipeUpAnimationLogic { } @Override - public void onUpdate(RectF currentRect, float progress) { + public void onUpdate(@Nullable AppCloseConfig config, RectF currentRect, float progress) { mHomeAnim.setPlayFraction(progress); mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect); mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL); float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius); + float alpha = getWindowAlpha(progress); + if (config != null && PROTOTYPE_APP_CLOSE.get()) { + alpha = config.getWindowAlpha(); + cornerRadius = config.getCornerRadius(); + } + if (mAnimationFactory.keepWindowOpaque()) { + alpha = 1f; + } mTransformParams - .setTargetAlpha(getWindowAlpha(progress)) + .setTargetAlpha(alpha) .setCornerRadius(cornerRadius); - mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); - mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius)); + mAnimationFactory.update(config, currentRect, progress, + mMatrix.mapRadius(cornerRadius)); } @Override diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java index 8fc453d303..1aa64fae1c 100644 --- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java @@ -52,6 +52,7 @@ import com.android.quickstep.OverviewComponentObserver; import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.SwipeUpAnimationLogic; import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; +import com.android.quickstep.util.AppCloseConfig; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TransformParams; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; @@ -306,11 +307,12 @@ abstract class SwipeUpGestureTutorialController extends TutorialController { } @Override - public void update(RectF rect, float progress, float radius) { + public void update(@Nullable AppCloseConfig config, RectF rect, float progress, + float radius) { mFakeIconView.setVisibility(View.VISIBLE); mFakeIconView.update(rect, progress, 1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */, - radius, + radius, 255, false, /* isOpening */ mFakeIconView, mDp, false /* isVerticalBarLayout */); diff --git a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java new file mode 100644 index 0000000000..bec33797a3 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.annotation.FloatRange; +import android.annotation.IntRange; + +/* + * Adds getter methods to {@link MultiValueUpdateListener} specific to app close animation, + * so that the entire animation can be defined in one place. + */ +public abstract class AppCloseConfig extends MultiValueUpdateListener { + + /** + * Returns the translation y of the workspace contents. + */ + public abstract float getWorkspaceTransY(); + + /* + * Returns the scale of the workspace contents. + */ + public abstract float getWorkspaceScale(); + + /* + * Returns the alpha of the window. + */ + public abstract @FloatRange(from = 0, to = 1) float getWindowAlpha(); + + /* + * Returns the alpha of the foreground layer of an adaptive icon. + */ + public abstract @IntRange(from = 0, to = 255) int getFgAlpha(); + + /* + * Returns the corner radius of the window and icon. + */ + public abstract float getCornerRadius(); + + /* + * Returns the interpolated progress of the animation. + */ + public abstract float getInterpolatedProgress(); + +} diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java index e5d2c53d60..02ec68a8d9 100644 --- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.RectF; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; @@ -241,7 +242,7 @@ public class RectFSpringAnim extends ReleaseCheck { mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight); } for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { - onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress); + onUpdateListener.onUpdate(null, mCurrentRect, mCurrentScaleProgress); } } } @@ -266,7 +267,7 @@ public class RectFSpringAnim extends ReleaseCheck { } public interface OnUpdateListener { - void onUpdate(RectF currentRect, float progress); + void onUpdate(@Nullable AppCloseConfig values, RectF currentRect, float progress); default void onCancel() { } } diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java new file mode 100644 index 0000000000..95d56aaca4 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2021 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 static com.android.launcher3.Utilities.dpToPx; +import static com.android.launcher3.anim.Interpolators.LINEAR; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.PathParser; +import android.util.Property; +import android.view.animation.Interpolator; + +import androidx.core.view.animation.PathInterpolatorCompat; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.DynamicResource; +import com.android.systemui.plugins.ResourceProvider; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Applies spring forces to animate from a starting rect to a target rect, + * while providing update callbacks to the caller. + */ +public class RectFSpringAnim2 extends RectFSpringAnim { + + private static final FloatPropertyCompat RECT_CENTER_X = + new FloatPropertyCompat("rectCenterXSpring") { + @Override + public float getValue(RectFSpringAnim2 anim) { + return anim.mCurrentCenterX; + } + + @Override + public void setValue(RectFSpringAnim2 anim, float currentCenterX) { + anim.mCurrentCenterX = currentCenterX; + anim.onUpdate(); + } + }; + + private static final FloatPropertyCompat RECT_Y = + new FloatPropertyCompat("rectYSpring") { + @Override + public float getValue(RectFSpringAnim2 anim) { + return anim.mCurrentY; + } + + @Override + public void setValue(RectFSpringAnim2 anim, float y) { + anim.mCurrentY = y; + anim.onUpdate(); + } + }; + + private static final Property PROGRESS = + new Property(Float.class, "rectFProgress") { + @Override + public Float get(RectFSpringAnim2 rectFSpringAnim) { + return rectFSpringAnim.mProgress; + } + + @Override + public void set(RectFSpringAnim2 rectFSpringAnim, Float progress) { + rectFSpringAnim.mProgress = progress; + rectFSpringAnim.onUpdate(); + } + }; + + private final RectF mStartRect; + private final RectF mTargetRect; + private final RectF mCurrentRect = new RectF(); + private final List mOnUpdateListeners = new ArrayList<>(); + private final List mAnimatorListeners = new ArrayList<>(); + + private float mCurrentCenterX; + private float mCurrentY; + + private float mTargetX; + private float mTargetY; + + // If true, tracking the bottom of the rects, else tracking the top. + private final boolean mTrackingBottomY; + private float mProgress; + private SpringAnimation mRectXAnim; + private SpringAnimation mRectYAnim; + private ValueAnimator mRectScaleAnim; + private boolean mAnimsStarted; + private boolean mRectXAnimEnded; + private boolean mRectYAnimEnded; + private boolean mRectScaleAnimEnded; + + private final float mXDamping; + private final float mXStiffness; + + private final float mYDamping; + private float mYStiffness; + + private long mDuration; + + private final Interpolator mCloseInterpolator; + + private AppCloseConfig mValues; + final float mStartRadius; + final float mEndRadius; + + final float mHomeTransYEnd; + final float mScaleStart; + + public RectFSpringAnim2(RectF startRect, RectF targetRect, Context context, float startRadius, + float endRadius) { + super(startRect, targetRect, context); + mStartRect = startRect; + mTargetRect = targetRect; + + mTrackingBottomY = startRect.bottom < targetRect.bottom; + mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top; + mCurrentCenterX = mStartRect.centerX(); + + mTargetY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top; + mTargetX = mTargetRect.centerX(); + + ResourceProvider rp = DynamicResource.provider(context); + mXDamping = rp.getFloat(R.dimen.swipe_up_rect_2_x_damping_ratio); + mXStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_x_stiffness); + + mYDamping = rp.getFloat(R.dimen.swipe_up_rect_2_y_damping_ratio); + mYStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_y_stiffness); + mDuration = Math.round(rp.getFloat(R.dimen.swipe_up_duration)); + + mHomeTransYEnd = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp)); + mScaleStart = rp.getFloat(R.dimen.swipe_up_scale_start); + + + if (!mTrackingBottomY) { + mYStiffness *= rp.getFloat(R.dimen.swipe_up_rect_2_y_stiffness_low_swipe_multiplier); + mDuration *= rp.getFloat(R.dimen.swipe_up_low_swipe_duration_multiplier); + } + + mCloseInterpolator = getAppCloseInterpolator(context); + + // End on a "round-enough" radius so that the shape reveal doesn't have to do too much + // rounding at the end of the animation. + mStartRadius = startRadius; + mEndRadius = endRadius; + + setCanRelease(true); + } + + public void onTargetPositionChanged() { + if (mRectXAnim != null && mTargetX != mTargetRect.centerX()) { + mTargetX = mTargetRect.centerX(); + mRectXAnim.animateToFinalPosition(mTargetX); + } + + if (mRectYAnim != null) { + if (mTrackingBottomY && mTargetY != mTargetRect.bottom) { + mTargetY = mTargetRect.bottom; + mRectYAnim.animateToFinalPosition(mTargetY); + } else if (!mTrackingBottomY && mTargetY != mTargetRect.top) { + mTargetY = mTargetRect.top; + mRectYAnim.animateToFinalPosition(mTargetY); + } + } + } + + public void addOnUpdateListener(OnUpdateListener onUpdateListener) { + mOnUpdateListeners.add(onUpdateListener); + } + + public void addAnimatorListener(Animator.AnimatorListener animatorListener) { + mAnimatorListeners.add(animatorListener); + } + + /** + * Starts the fling/spring animation. + * @param context The activity context. + * @param velocityPxPerMs Velocity of swipe in px/ms. + */ + public void start(Context context, PointF velocityPxPerMs) { + DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context); + + mRectXAnim = new SpringAnimation(this, RECT_CENTER_X) + .setStartValue(mCurrentCenterX) + .setMinValue(Math.min(0, mCurrentCenterX)) + .setMaxValue(Math.max(dp.widthPx, mCurrentCenterX)) + .setStartVelocity(velocityPxPerMs.x * 1000) + .setSpring(new SpringForce(mTargetX) + .setStiffness(mXStiffness) + .setDampingRatio(mXDamping)); + mRectXAnim.addEndListener(((animation, canceled, centerX, velocityX) -> { + mRectXAnimEnded = true; + maybeOnEnd(); + })); + + mRectYAnim = new SpringAnimation(this, RECT_Y) + .setStartValue(mCurrentY) + .setMinValue(Math.min(0, mCurrentY)) + .setMaxValue(Math.max(dp.heightPx, mCurrentY)) + .setStartVelocity(velocityPxPerMs.y * 1000) + .setSpring(new SpringForce(mTargetY) + .setStiffness(mYStiffness) + .setDampingRatio(mYDamping)); + mRectYAnim.addEndListener(((animation, canceled, centerY, velocityY) -> { + mRectYAnimEnded = true; + maybeOnEnd(); + })); + + mRectScaleAnim = ObjectAnimator.ofFloat(this, PROGRESS, 0, 1f) + .setDuration(mDuration); + mRectScaleAnim.setInterpolator(mCloseInterpolator); + mRectScaleAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRectScaleAnimEnded = true; + maybeOnEnd(); + } + }); + + mValues = buildConfig(); + mRectScaleAnim.addUpdateListener(mValues); + + setCanRelease(false); + mAnimsStarted = true; + + mRectXAnim.start(); + mRectYAnim.start(); + mRectScaleAnim.start(); + for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { + animatorListener.onAnimationStart(null); + } + } + + private AppCloseConfig buildConfig() { + return new AppCloseConfig() { + FloatProp mHomeTransY = new FloatProp(0, mHomeTransYEnd, 0, mDuration, LINEAR); + FloatProp mHomeScale = new FloatProp(mScaleStart, 1f, 0, mDuration, LINEAR); + FloatProp mWindowFadeOut = new FloatProp(1f, 0f, 0, 116, LINEAR); + // There should be a slight overlap b/w window fading out and fg fading in. + // (fg startDelay < window fade out duration) + FloatProp mFgFadeIn = new FloatProp(0, 255f, 100, mDuration - 100, LINEAR); + FloatProp mRadius = new FloatProp(mStartRadius, mEndRadius, 0, mDuration, LINEAR); + FloatProp mThreePointInterpolation = new FloatProp(0, 1, 0, mDuration, LINEAR); + + @Override + public float getWorkspaceTransY() { + return mHomeTransY.value; + } + + @Override + public float getWorkspaceScale() { + return mHomeScale.value; + } + + @Override + public float getWindowAlpha() { + return mWindowFadeOut.value; + } + + @Override + public int getFgAlpha() { + return (int) mFgFadeIn.value; + } + + @Override + public float getCornerRadius() { + return mRadius.value; + } + + @Override + public float getInterpolatedProgress() { + return mThreePointInterpolation.value; + } + + @Override + public void onUpdate(float percent) {} + }; + } + + public void end() { + if (mAnimsStarted) { + if (mRectXAnim.canSkipToEnd()) { + mRectXAnim.skipToEnd(); + } + if (mRectYAnim.canSkipToEnd()) { + mRectYAnim.skipToEnd(); + } + mRectScaleAnim.end(); + } + mRectXAnimEnded = true; + mRectYAnimEnded = true; + mRectScaleAnimEnded = true; + maybeOnEnd(); + } + + private boolean isEnded() { + return mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded; + } + + private void onUpdate() { + if (isEnded()) { + // Prevent further updates from being called. This can happen between callbacks for + // ending the x/y/scale animations. + return; + } + + if (!mOnUpdateListeners.isEmpty()) { + float rectProgress = mProgress; + float currentWidth = Utilities.mapRange(rectProgress, mStartRect.width(), + mTargetRect.width()); + float currentHeight = Utilities.mapRange(rectProgress, mStartRect.height(), + mTargetRect.height()); + if (mTrackingBottomY) { + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight, + mCurrentCenterX + currentWidth / 2, mCurrentY); + } else { + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY, + mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight); + } + + float currentPlayTime = mRectScaleAnimEnded ? mRectScaleAnim.getDuration() + : mRectScaleAnim.getCurrentPlayTime(); + float linearProgress = Math.min(1f, currentPlayTime / mRectScaleAnim.getDuration()); + for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { + onUpdateListener.onUpdate(mValues, mCurrentRect, linearProgress); + } + } + } + + private void maybeOnEnd() { + if (mAnimsStarted && isEnded()) { + mAnimsStarted = false; + setCanRelease(true); + for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { + animatorListener.onAnimationEnd(null); + } + } + } + + public void cancel() { + if (mAnimsStarted) { + for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { + onUpdateListener.onCancel(); + } + } + end(); + } + + private Interpolator getAppCloseInterpolator(Context context) { + ResourceProvider rp = DynamicResource.provider(context); + String path = String.format("M 0,0 C %f, %f, %f, %f, %f, %f C %f, %f, %f, %f, 1, 1", + rp.getFloat(R.dimen.c1_a), + rp.getFloat(R.dimen.c1_b), + rp.getFloat(R.dimen.c1_c), + rp.getFloat(R.dimen.c1_d), + rp.getFloat(R.dimen.mp_x), + rp.getFloat(R.dimen.mp_y), + rp.getFloat(R.dimen.c2_a), + rp.getFloat(R.dimen.c2_b), + rp.getFloat(R.dimen.c2_c), + rp.getFloat(R.dimen.c2_d)); + return PathInterpolatorCompat.create(PathParser.createPathFromPathData(path)); + } +} diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 1df459ef8c..de6c4f5e1c 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER; +import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE; import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER; import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW; import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM; @@ -220,6 +221,9 @@ public class StaggeredWorkspaceAnim { * @param totalRows Total number of rows. */ private void addStaggeredAnimationForView(View v, int row, int totalRows) { + if (PROTOTYPE_APP_CLOSE.get()) { + 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. diff --git a/res/values/config.xml b/res/values/config.xml index db9881161b..b75af7f9e4 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -134,8 +134,45 @@ 200 1.5 + + 0.98 + 500 + + 3 + 3 + + 0.4 + 200 + 0.8 - 200 + 100 + + + 1 + 350 + + 1 + 700 + + 1 + 1 + + 0.85 + + + 0.05 + 0 + 0.133333 + 0.06 + + 0.166666 + .4 + + 0.208333 + .82 + .25 + 1 + 0.7 150 @@ -151,35 +188,32 @@ -60dp - @dimen/all_apps_spring_damping_ratio - @dimen/all_apps_spring_stiffness + @dimen/swipe_up_duration + @dimen/swipe_up_scale_start + @dimen/swipe_up_trans_y_dp + @dimen/swipe_up_trans_y_dp_per_s + @dimen/swipe_up_trans_y_damping + @dimen/swipe_up_trans_y_stiffness + @dimen/swipe_up_rect_2_x_damping_ratio + @dimen/swipe_up_rect_2_x_stiffness + @dimen/swipe_up_rect_2_y_damping_ratio + @dimen/swipe_up_rect_2_y_stiffness + @dimen/swipe_up_launcher_alpha_max_progress + @dimen/swipe_up_rect_2_y_stiffness_low_swipe_multiplier + @dimen/swipe_up_low_swipe_duration_multiplier - @dimen/dismiss_task_trans_y_damping_ratio - @dimen/dismiss_task_trans_y_stiffness + @dimen/c1_a + @dimen/c1_b + @dimen/c1_c + @dimen/c1_d - @dimen/dismiss_task_trans_x_damping_ratio - @dimen/dismiss_task_trans_x_stiffness + @dimen/mp_x + @dimen/mp_y - @dimen/horizontal_spring_damping_ratio - @dimen/horizontal_spring_stiffness - - @dimen/swipe_up_rect_scale_damping_ratio - @dimen/swipe_up_rect_scale_stiffness - - @dimen/swipe_up_rect_xy_fling_friction - @dimen/swipe_up_rect_xy_damping_ratio - @dimen/swipe_up_rect_xy_stiffness - - @dimen/staggered_damping_ratio - @dimen/staggered_stiffness - @dimen/unlock_staggered_velocity_dp_per_s - - @dimen/swipe_up_fling_min_visible_change - @dimen/swipe_up_y_overshoot - - @dimen/hint_scale_damping_ratio - @dimen/hint_scale_stiffness - @dimen/hint_scale_velocity_dp_per_s + @dimen/c2_a + @dimen/c2_b + @dimen/c2_c + @dimen/c2_d diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 10091a18b8..5d941a71de 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3323,6 +3323,18 @@ public class Workspace extends PagedView } } + /** + * Set the given view's pivot point to match the workspace's, so that it scales together. Since + * both this view and workspace can move, transform the point manually instead of using + * dragLayer.getDescendantCoordRelativeToSelf and related methods. + */ + public void setPivotToScaleWithSelf(View sibling) { + sibling.setPivotY(getPivotY() + getTop() + - sibling.getTop() - sibling.getTranslationY()); + sibling.setPivotX(getPivotX() + getLeft() + - sibling.getLeft() - sibling.getTranslationX()); + } + @Override public int getExpectedHeight() { return getMeasuredHeight() <= 0 || !mIsLayoutValid diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index f4986f4417..ed854dcd0e 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -119,7 +119,7 @@ public class WorkspaceStateTransitionAnimation { propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator); } - setPivotToScaleWithWorkspace(hotseat); + mWorkspace.setPivotToScaleWithSelf(hotseat); float hotseatScale = hotseatScaleAndTranslation.scale; if (shouldSpring) { PendingAnimation pa = (PendingAnimation) propertySetter; @@ -156,18 +156,6 @@ public class WorkspaceStateTransitionAnimation { } } - /** - * Set the given view's pivot point to match the workspace's, so that it scales together. Since - * both this view and workspace can move, transform the point manually instead of using - * dragLayer.getDescendantCoordRelativeToSelf and related methods. - */ - private void setPivotToScaleWithWorkspace(View sibling) { - sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - - sibling.getTop() - sibling.getTranslationY()); - sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft() - - sibling.getLeft() - sibling.getTranslationX()); - } - public void setScrim(PropertySetter propertySetter, LauncherState state, StateAnimationConfig config) { Scrim workspaceDragScrim = mLauncher.getDragLayer().getWorkspaceDragScrim(); diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java index 7a389374dc..11e831ee4c 100644 --- a/src/com/android/launcher3/anim/Interpolators.java +++ b/src/com/android/launcher3/anim/Interpolators.java @@ -27,7 +27,6 @@ import android.view.animation.PathInterpolator; import com.android.launcher3.Utilities; - /** * Common interpolators used in Launcher */ diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 44e31389ae..272dfc2cf0 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -233,6 +233,9 @@ public final class FeatureFlags { public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false, "Sends a notification whenever launcher encounters an uncaught exception."); + public static final BooleanFlag PROTOTYPE_APP_CLOSE = getDebugFlag( + "PROTOTYPE_APP_CLOSE", false, "Enables new app close"); + public static void initialize(Context context) { synchronized (sDebugFlags) { for (DebugFlag flag : sDebugFlags) { diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java index 4e82336b09..a66b3f942e 100644 --- a/src/com/android/launcher3/views/ClipIconView.java +++ b/src/com/android/launcher3/views/ClipIconView.java @@ -15,10 +15,13 @@ */ package com.android.launcher3.views; +import static com.android.launcher3.Utilities.boundToRange; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; +import static java.lang.Math.max; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -143,10 +146,9 @@ public class ClipIconView extends View implements ClipPathView { /** * Update the icon UI to match the provided parameters during an animation frame */ - public void update(RectF rect, float progress, float shapeProgressStart, - float cornerRadius, boolean isOpening, View container, - DeviceProfile dp, boolean isVerticalBarLayout) { - + public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius, + int fgIconAlpha, boolean isOpening, View container, DeviceProfile dp, + boolean isVerticalBarLayout) { MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams(); float dX = mIsRtl @@ -166,7 +168,7 @@ public class ClipIconView extends View implements ClipPathView { return; } - update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale, + update(rect, progress, shapeProgressStart, cornerRadius, fgIconAlpha, isOpening, scale, minSize, lp, isVerticalBarLayout, dp); container.setPivotX(0); @@ -178,8 +180,8 @@ public class ClipIconView extends View implements ClipPathView { } private void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius, - boolean isOpening, float scale, float minSize, MarginLayoutParams parentLp, - boolean isVerticalBarLayout, DeviceProfile dp) { + int fgIconAlpha, boolean isOpening, float scale, float minSize, + MarginLayoutParams parentLp, boolean isVerticalBarLayout, DeviceProfile dp) { float dX = mIsRtl ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width) : rect.left - parentLp.getMarginStart(); @@ -187,9 +189,9 @@ public class ClipIconView extends View implements ClipPathView { // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f; - float shapeRevealProgress = Utilities.boundToRange(mapToRange( - Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax, - LINEAR), 0, 1); + + float shapeRevealProgress = boundToRange(mapToRange(max(shapeProgressStart, progress), + shapeProgressStart, 1f, 0, toMax, LINEAR), 0, 1); if (isVerticalBarLayout) { mOutline.right = (int) (rect.width() / scale); @@ -231,6 +233,8 @@ public class ClipIconView extends View implements ClipPathView { sTmpRect.offset(diffX, diffY); mForeground.setBounds(sTmpRect); } else { + mForeground.setAlpha(fgIconAlpha); + // Spring the foreground relative to the icon's movement within the DragLayer. int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR); int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR); diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index 96268cefb2..d49320b073 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -100,6 +100,8 @@ public class FloatingIconView extends FrameLayout implements private ListenerView mListenerView; private Runnable mFastFinishRunnable; + private float mIconOffsetY; + public FloatingIconView(Context context) { this(context, null); } @@ -136,16 +138,18 @@ public class FloatingIconView extends FrameLayout implements /** * Positions this view to match the size and location of {@param rect}. - * @param alpha The alpha to set this view. + * @param alpha The alpha[0, 1] of the entire floating view. + * @param fgIconAlpha The alpha[0-255] of the foreground layer of the icon (if applicable). * @param progress A value from [0, 1] that represents the animation progress. * @param shapeProgressStart The progress value at which to start the shape reveal. * @param cornerRadius The corner radius of {@param rect}. + * @param isOpening True if view is used for app open animation, false for app close animation. */ - public void update(RectF rect, float alpha, float progress, float shapeProgressStart, - float cornerRadius, boolean isOpening) { + public void update(float alpha, int fgIconAlpha, RectF rect, float progress, + float shapeProgressStart, float cornerRadius, boolean isOpening) { setAlpha(alpha); - mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, - this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout); + mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, fgIconAlpha, + isOpening, this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout); } @Override @@ -478,11 +482,19 @@ public class FloatingIconView extends FrameLayout implements @Override public void onAnimationRepeat(Animator animator) {} + /** + * Offsets and updates the position of this view by {@param y}. + */ + public void setPositionOffsetY(float y) { + mIconOffsetY = y; + onGlobalLayout(); + } + @Override public void onGlobalLayout() { - if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) { - getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, - sTmpRectF); + if (mOriginalIcon != null && mOriginalIcon.isAttachedToWindow() && mPositionOut != null) { + getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, sTmpRectF); + sTmpRectF.offset(0, mIconOffsetY); if (!sTmpRectF.equals(mPositionOut)) { updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams()); if (mOnTargetChangeRunnable != null) { @@ -617,6 +629,7 @@ public class FloatingIconView extends FrameLayout implements mClipIconView.recycle(); mBtvDrawable.setBackground(null); mFastFinishRunnable = null; + mIconOffsetY = 0; } private static class IconLoadResult {