diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 085b9b3af9..5ccc1e8681 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; import static com.android.quickstep.views.RecentsView.TASK_MODALNESS; @@ -57,7 +58,7 @@ public final class RecentsViewStateController extends mRecentsView.updateEmptyMessage(); mRecentsView.resetTaskVisuals(); } - setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state); + setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state); mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress()); } @@ -75,17 +76,19 @@ public final class RecentsViewStateController extends AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals)); } - setAlphas(builder, toState); + setAlphas(builder, config, toState); builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS, toState.getOverviewFullscreenProgress(), LINEAR); } - private void setAlphas(PropertySetter propertySetter, LauncherState state) { + private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config, + LauncherState state) { float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0; propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA, buttonAlpha, LINEAR); propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(), - MultiValueAlpha.VALUE, buttonAlpha, LINEAR); + MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator( + ANIM_OVERVIEW_ACTIONS_FADE, LINEAR)); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 131fcbfbf1..daa1aadbae 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -25,6 +25,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator; import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; @@ -163,10 +164,15 @@ public class QuickstepAtomicAnimationFactory extends config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL); config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); - config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); - config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); - Workspace workspace = mActivity.getWorkspace(); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL); + if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) { + config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME); + } else { + config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); + } + + Workspace workspace = mActivity.getWorkspace(); // Start from a higher workspace scale, but only if we're invisible so we don't jump. boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE; if (isWorkspaceVisible) { @@ -206,8 +212,10 @@ public class QuickstepAtomicAnimationFactory extends config.setInterpolator(ANIM_WORKSPACE_SCALE, fromState == NORMAL ? ACCEL : OVERSHOOT_1_2); config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL); + config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT); } else { config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2); + config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); // Scale up the recents, if it is not coming from the side RecentsView overview = mActivity.getOverviewPanel(); @@ -225,7 +233,6 @@ public class QuickstepAtomicAnimationFactory extends : OVERSHOOT_1_7; config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator); - config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2); } else if (fromState == HINT_STATE && toState == NORMAL) { config.setInterpolator(ANIM_DEPTH, DEACCEL_3); if (mHintToNormalDuration == -1) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index c1a585ea9d..61f6702dcc 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -19,13 +19,13 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU; 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.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; -import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.ValueAnimator; @@ -45,6 +45,7 @@ import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.testing.TestProtocol; @@ -52,7 +53,9 @@ import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; +import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.AssistantUtilities; +import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -63,6 +66,8 @@ public class NavBarToHomeTouchController implements TouchController, SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; + // How much of the overview scrim we can remove during the transition. + private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f; private final Launcher mLauncher; private final SingleAxisSwipeDetector mSwipeDetector; @@ -156,8 +161,13 @@ public class NavBarToHomeTouchController implements TouchController, final PendingAnimation builder = new PendingAnimation(accuracy); if (mStartState.overviewUi) { RecentsView recentsView = mLauncher.getOverviewPanel(); - builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET, - -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR); + AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher, + builder); + float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS, + mStartState.getOverviewScrimAlpha(mLauncher), + mEndState.getOverviewScrimAlpha(mLauncher)); + builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(), + OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { builder.addOnFrameCallback( () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); @@ -212,8 +222,13 @@ public class NavBarToHomeTouchController implements TouchController, recentsView.switchToScreenshot(null, () -> recentsView.finishRecentsAnimation(true /* toRecents */, null)); } - mLauncher.getStateManager().goToState(mEndState, true, - () -> onSwipeInteractionCompleted(mEndState)); + if (mStartState == OVERVIEW) { + new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState)) + .animateWithVelocity(velocity); + } else { + mLauncher.getStateManager().goToState(mEndState, true, + () -> onSwipeInteractionCompleted(mEndState)); + } if (mStartState != mEndState) { logStateChange(mStartState.containerType, logAction); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 9316938c45..dbff20ae6c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -21,11 +21,8 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; -import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK; import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.graphics.PointF; @@ -35,14 +32,15 @@ import android.view.MotionEvent; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.graphics.OverviewScrim; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.VibratorWrapper; -import com.android.quickstep.util.StaggeredWorkspaceAnim; +import com.android.quickstep.util.AnimatorControllerWithResistance; +import com.android.quickstep.util.OverviewToHomeAnim; import com.android.quickstep.views.RecentsView; /** @@ -62,10 +60,10 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo private boolean mDidTouchStartInNavBar; private boolean mReachedOverview; - private boolean mIsOverviewRehidden; - private boolean mIsHomeStaggeredAnimFinished; // The last recorded displacement before we reached overview. private PointF mStartDisplacement = new PointF(); + private float mStartY; + private AnimatorPlaybackController mOverviewResistYAnim; // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator. private ObjectAnimator mNormalToHintOverviewScrimAnimator; @@ -123,6 +121,7 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo mToState.getOverviewScrimAlpha(mLauncher)); } mReachedOverview = false; + mOverviewResistYAnim = null; } @Override @@ -160,6 +159,9 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo mNormalToHintOverviewScrimAnimator = null; mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> { mLauncher.getStateManager().goToState(OVERVIEW, true, () -> { + mOverviewResistYAnim = AnimatorControllerWithResistance + .createRecentsResistanceFromOverviewAnim(mLauncher, null) + .createPlaybackController(); mReachedOverview = true; maybeSwipeInteractionToOverviewComplete(); }); @@ -173,13 +175,6 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo } } - // Used if flinging back to home after reaching overview - private void maybeSwipeInteractionToHomeComplete() { - if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) { - onSwipeInteractionCompleted(NORMAL, Touch.FLING); - } - } - @Override protected boolean handlingOverviewAnim() { return mDidTouchStartInNavBar && super.handlingOverviewAnim(); @@ -193,11 +188,17 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo if (mMotionPauseDetector.isPaused()) { if (!mReachedOverview) { mStartDisplacement.set(xDisplacement, yDisplacement); + mStartY = event.getY(); } else { mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x) * OVERVIEW_MOVEMENT_FACTOR); - mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y) - * OVERVIEW_MOVEMENT_FACTOR); + float yProgress = (mStartDisplacement.y - yDisplacement) / mStartY; + if (yProgress > 0 && mOverviewResistYAnim != null) { + mOverviewResistYAnim.setPlayFraction(yProgress); + } else { + mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y) + * OVERVIEW_MOVEMENT_FACTOR); + } } // Stay in Overview. return true; @@ -212,35 +213,8 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo StateManager stateManager = mLauncher.getStateManager(); boolean goToHomeInsteadOfOverview = isFling; if (goToHomeInsteadOfOverview) { - if (velocity > 0) { - stateManager.goToState(NORMAL, true, - () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING)); - } else { - mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false; - - StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim( - mLauncher, velocity, false /* animateOverviewScrim */); - staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - mIsHomeStaggeredAnimFinished = true; - maybeSwipeInteractionToHomeComplete(); - } - }).start(); - - // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here. - stateManager.cancelAnimation(); - StateAnimationConfig config = new StateAnimationConfig(); - config.duration = OVERVIEW.getTransitionDuration(mLauncher); - config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK; - AnimatorSet anim = stateManager.createAtomicAnimation( - stateManager.getState(), NORMAL, config); - anim.addListener(AnimationSuccessListener.forRunnable(() -> { - mIsOverviewRehidden = true; - maybeSwipeInteractionToHomeComplete(); - })); - anim.start(); - } + new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING)) + .animateWithVelocity(velocity); } if (mReachedOverview) { float distanceDp = dpiFromPx(Math.max( @@ -256,6 +230,13 @@ public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchCo .withEndAction(goToHomeInsteadOfOverview ? null : this::maybeSwipeInteractionToOverviewComplete); + if (!goToHomeInsteadOfOverview) { + // Return to normal properties for the overview state. + StateAnimationConfig config = new StateAnimationConfig(); + config.duration = duration; + LauncherState state = mLauncher.getStateManager().getState(); + mLauncher.getStateManager().createAtomicAnimation(state, state, config).start(); + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java new file mode 100644 index 0000000000..d2e1dedd2b --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2020 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.LauncherState.NORMAL; +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.DEACCEL; +import static com.android.launcher3.anim.Interpolators.FINAL_FRAME; +import static com.android.launcher3.anim.Interpolators.INSTANT; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X; +import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y; +import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.util.Log; +import android.view.animation.Interpolator; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.states.StateAnimationConfig; +import com.android.quickstep.views.RecentsView; + +/** + * Runs an animation from overview to home. Currently, this animation is just a wrapper around the + * normal state transition, in order to keep RecentsView at the same scale and translationY that + * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play + * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling. + */ +public class OverviewToHomeAnim { + + private static final String TAG = "OverviewToHomeAnim"; + + // Constants to specify how to scroll RecentsView to the default page if it's not already there. + private static final int DEFAULT_PAGE = 0; + private static final int PER_PAGE_SCROLL_DURATION = 150; + private static final int MAX_PAGE_SCROLL_DURATION = 750; + + private final Launcher mLauncher; + private final Runnable mOnReachedHome; + + // Only run mOnReachedHome when both of these are true. + private boolean mIsHomeStaggeredAnimFinished; + private boolean mIsOverviewHidden; + + public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) { + mLauncher = launcher; + mOnReachedHome = onReachedHome; + } + + /** + * Starts the animation. If velocity < 0 (i.e. upwards), also plays a + * {@link StaggeredWorkspaceAnim}. + */ + public void animateWithVelocity(float velocity) { + StateManager stateManager = mLauncher.getStateManager(); + LauncherState startState = stateManager.getState(); + if (startState != OVERVIEW) { + Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState); + } + + boolean playStaggeredWorkspaceAnim = velocity < 0; + if (playStaggeredWorkspaceAnim) { + StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim( + mLauncher, velocity, false /* animateOverviewScrim */); + staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mIsHomeStaggeredAnimFinished = true; + maybeOverviewToHomeAnimComplete(); + } + }).start(); + } else { + mIsHomeStaggeredAnimFinished = true; + } + + RecentsView recentsView = mLauncher.getOverviewPanel(); + int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE; + int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION, + numPagesToScroll * PER_PAGE_SCROLL_DURATION); + int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher)); + + StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig(); + config.duration = duration; + config.animFlags = playStaggeredWorkspaceAnim + // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here. + ? PLAY_ATOMIC_OVERVIEW_PEEK + : ANIM_ALL_COMPONENTS; + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, DEACCEL); + config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME); + config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME); + config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT); + AnimatorSet anim = stateManager.createAtomicAnimation( + startState, NORMAL, config); + anim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mIsOverviewHidden = true; + maybeOverviewToHomeAnimComplete(); + } + }); + stateManager.cancelAnimation(); + anim.start(); + recentsView.snapToPage(DEFAULT_PAGE, duration); + } + + private void maybeOverviewToHomeAnimComplete() { + if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) { + mOnReachedHome.run(); + } + } + + /** + * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are + * already set. This ensures they aren't overridden before being used. + */ + private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig { + @Override + public void setInterpolator(int animId, Interpolator interpolator) { + if (mInterpolators[animId] == null || interpolator == null) { + super.setInterpolator(animId, interpolator); + } + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index 79d57c55a2..1bf2fbf81f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -105,6 +105,7 @@ public class OverviewActionsView extends FrameLayo public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr, 0); mMultiValueAlpha = new MultiValueAlpha(this, 4); + mMultiValueAlpha.setUpdateVisibility(true); } @Override @@ -168,7 +169,6 @@ public class OverviewActionsView extends FrameLayo } boolean isHidden = mHiddenFlags != 0; mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1); - setVisibility(isHidden ? INVISIBLE : VISIBLE); } /** diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java index 23b02d5665..a19a67c8fc 100644 --- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java +++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java @@ -18,6 +18,8 @@ package com.android.quickstep.util; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; +import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION; import android.animation.TimeInterpolator; import android.content.Context; @@ -27,12 +29,16 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.FloatProperty; +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.views.RecentsView; /** * Controls an animation that can go beyond progress = 1, at which point resistance should be @@ -48,15 +54,32 @@ public class AnimatorControllerWithResistance { */ public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f; - /** - * Start slowing down the rate of scaling down when recents view is smaller than this scale. - */ - private static final float RECENTS_SCALE_START_RESIST = 0.75f; + private enum RecentsParams { + FROM_APP(0.75f, 0.5f, 1f), + FROM_OVERVIEW(1f, 0.75f, 0.5f); - /** - * Recents view will reach this scale at the very end of the drag. - */ - private static final float RECENTS_SCALE_MAX_RESIST = 0.5f; + RecentsParams(float scaleStartResist, float scaleMaxResist, float translationFactor) { + this.scaleStartResist = scaleStartResist; + this.scaleMaxResist = scaleMaxResist; + this.translationFactor = translationFactor; + } + + /** + * Start slowing down the rate of scaling down when recents view is smaller than this scale. + */ + public final float scaleStartResist; + + /** + * Recents view will reach this scale at the very end of the drag. + */ + public final float scaleMaxResist; + + /** + * How much translation to apply to RecentsView when the drag reaches the top of the screen, + * where 0 will keep it centered and 1 will have it barely touch the top of the screen. + */ + public final float translationFactor; + } private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL; private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR; @@ -115,6 +138,24 @@ public class AnimatorControllerWithResistance { RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, FloatProperty scaleProperty, TRANSLATION translationTarget, FloatProperty translationProperty) { + + PendingAnimation resistAnim = createRecentsResistanceAnim(null, context, + recentsOrientedState, dp, scaleTarget, scaleProperty, translationTarget, + translationProperty, RecentsParams.FROM_APP); + + AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController(); + return new AnimatorControllerWithResistance(normalController, resistanceController); + } + + /** + * Creates the resistance animation for {@link #createForRecents}, or can be used separately + * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}. + */ + public static PendingAnimation createRecentsResistanceAnim( + @Nullable PendingAnimation resistAnim, Context context, + RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget, + FloatProperty scaleProperty, TRANSLATION translationTarget, + FloatProperty translationProperty, RecentsParams params) { Rect startRect = new Rect(); LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, startRect, recentsOrientedState.getOrientationHandler()); @@ -125,7 +166,9 @@ public class AnimatorControllerWithResistance { distanceToCover = (long) ((dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR); } - PendingAnimation resistAnim = new PendingAnimation(distanceToCover * 2); + if (resistAnim == null) { + resistAnim = new PendingAnimation(distanceToCover * 2); + } PointF pivot = new PointF(); float fullscreenScale = recentsOrientedState.getFullScreenScaleAndPivot( @@ -141,9 +184,9 @@ public class AnimatorControllerWithResistance { } else { // Create an interpolator that resists the scale so the scale doesn't get smaller than // RECENTS_SCALE_MAX_RESIST. - float startResist = Utilities.getProgress(RECENTS_SCALE_START_RESIST, startScale, + float startResist = Utilities.getProgress(params.scaleStartResist , startScale, endScale); - float maxResist = Utilities.getProgress(RECENTS_SCALE_MAX_RESIST, startScale, endScale); + float maxResist = Utilities.getProgress(params.scaleMaxResist, startScale, endScale); scaleInterpolator = t -> { if (t < startResist) { return t; @@ -160,17 +203,28 @@ public class AnimatorControllerWithResistance { // Compute where the task view would be based on the end scale, if we didn't translate. RectF endRectF = new RectF(startRect); Matrix temp = new Matrix(); - temp.setScale(RECENTS_SCALE_MAX_RESIST, RECENTS_SCALE_MAX_RESIST, pivot.x, pivot.y); + temp.setScale(params.scaleMaxResist, params.scaleMaxResist, pivot.x, pivot.y); temp.mapRect(endRectF); // Translate such that the task view touches the top of the screen when drag does. float endTranslation = endRectF.top * recentsOrientedState.getOrientationHandler() - .getSecondaryTranslationDirectionFactor(); + .getSecondaryTranslationDirectionFactor() * params.translationFactor; resistAnim.addFloat(translationTarget, translationProperty, 0, endTranslation, RECENTS_TRANSLATE_RESIST_INTERPOLATOR); } - AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController(); - return new AnimatorControllerWithResistance(normalController, resistanceController); + return resistAnim; } + /** + * Helper method to update or create a PendingAnimation suitable for animating + * a RecentsView interaction that started from the overview state. + */ + public static PendingAnimation createRecentsResistanceFromOverviewAnim( + BaseDraggingActivity activity, @Nullable PendingAnimation resistanceAnim) { + RecentsView recentsView = activity.getOverviewPanel(); + return createRecentsResistanceAnim(resistanceAnim, activity, + recentsView.getPagedViewOrientedState(), activity.getDeviceProfile(), + recentsView, RECENTS_SCALE_PROPERTY, recentsView, TASK_SECONDARY_TRANSLATION, + RecentsParams.FROM_OVERVIEW); + } } diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java index f90ad3cd0b..8b7217709f 100644 --- a/src/com/android/launcher3/states/StateAnimationConfig.java +++ b/src/com/android/launcher3/states/StateAnimationConfig.java @@ -71,6 +71,7 @@ public class StateAnimationConfig { ANIM_ALL_APPS_HEADER_FADE, ANIM_OVERVIEW_MODAL, ANIM_DEPTH, + ANIM_OVERVIEW_ACTIONS_FADE, }) @Retention(RetentionPolicy.SOURCE) public @interface AnimType {} @@ -89,10 +90,11 @@ public class StateAnimationConfig { public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions public static final int ANIM_OVERVIEW_MODAL = 13; public static final int ANIM_DEPTH = 14; + public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15; - private static final int ANIM_TYPES_COUNT = 15; + private static final int ANIM_TYPES_COUNT = 16; - private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT]; + protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT]; public StateAnimationConfig() { } diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java index a8642b05c0..5be95292b4 100644 --- a/src/com/android/launcher3/util/MultiValueAlpha.java +++ b/src/com/android/launcher3/util/MultiValueAlpha.java @@ -19,6 +19,8 @@ package com.android.launcher3.util; import android.util.FloatProperty; import android.view.View; +import com.android.launcher3.anim.AlphaUpdateListener; + import java.util.Arrays; /** @@ -44,6 +46,8 @@ public class MultiValueAlpha { private final AlphaProperty[] mMyProperties; private int mValidMask; + // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values. + private boolean mUpdateVisibility; public MultiValueAlpha(View view, int size) { mView = view; @@ -66,6 +70,11 @@ public class MultiValueAlpha { return mMyProperties[index]; } + /** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */ + public void setUpdateVisibility(boolean updateVisibility) { + mUpdateVisibility = updateVisibility; + } + public class AlphaProperty { private final int mMyMask; @@ -99,6 +108,9 @@ public class MultiValueAlpha { mValue = value; mView.setAlpha(mOthers * mValue); + if (mUpdateVisibility) { + AlphaUpdateListener.updateVisibility(mView); + } } public float getValue() {