diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index e62de186f8..98eb29aa67 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -42,7 +42,6 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.StateListener; -import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; @@ -172,7 +171,7 @@ public class LauncherRecentsView extends RecentsView mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen)); ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt( - mActivity.findViewById(R.id.scrim_view), ScrimView.DRAG_HANDLE_ALPHA, 0); + mActivity.getScrimView(), ScrimView.DRAG_HANDLE_ALPHA, 0); dragHandleAnim.setInterpolator(Interpolators.ACCEL_2); anim.play(dragHandleAnim); diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5b9f676bbd..fb8bd455c0 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1193,7 +1193,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Setup the drag controller (drop targets have to be added in reverse order in priority) mDropTargetBar.setup(mDragController); - mAllAppsController.setupViews(mAppsView); + mAllAppsController.setupViews(mAppsView, mScrimView); } /** @@ -1415,6 +1415,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return mDropTargetBar; } + public ScrimView getScrimView() { + return mScrimView; + } + public LauncherAppWidgetHost getAppWidgetHost() { return mAppWidgetHost; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 8bc0242920..190ec162ee 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -125,7 +125,7 @@ public class Workspace extends PagedView private static final int ADJACENT_SCREEN_DROP_DURATION = 300; - private static final int DEFAULT_PAGE = 0; + public static final int DEFAULT_PAGE = 0; private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 00128eb033..68b070617d 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -212,9 +212,9 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd); } - public void setupViews(AllAppsContainerView appsView) { + public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) { mAppsView = appsView; - mScrimView = mLauncher.findViewById(R.id.scrim_view); + mScrimView = scrimView; } /** diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java index 290dbb6baa..43f30f1e07 100644 --- a/src/com/android/launcher3/states/HintState.java +++ b/src/com/android/launcher3/states/HintState.java @@ -17,6 +17,7 @@ package com.android.launcher3.states; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; +import com.android.launcher3.LauncherStateManager; import com.android.launcher3.Workspace; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -50,8 +51,13 @@ public class HintState extends LauncherState { @Override public void onStateTransitionEnd(Launcher launcher) { - launcher.getStateManager().goToState(NORMAL); + LauncherStateManager stateManager = launcher.getStateManager(); Workspace workspace = launcher.getWorkspace(); - workspace.post(workspace::moveToDefaultScreen); + boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE; + stateManager.goToState(NORMAL, true, willMoveScreens ? null + : launcher.getScrimView()::startDragHandleEducationAnim); + if (willMoveScreens) { + workspace.post(workspace::moveToDefaultScreen); + } } } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index adad097217..39e1eac758 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -22,8 +22,10 @@ import static androidx.core.graphics.ColorUtils.compositeColors; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; -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.LINEAR; +import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.animation.Animator; @@ -100,6 +102,12 @@ public class ScrimView extends View implements Insettable, O private static final int SETTINGS = R.string.settings_button_text; private static final int ALPHA_CHANNEL_COUNT = 1; + private static final long DRAG_HANDLE_BOUNCE_DURATION_MS = 300; + // How much to delay before repeating the bounce. + private static final long DRAG_HANDLE_BOUNCE_DELAY_MS = 200; + // Repeat this many times (i.e. total number of bounces is 1 + this). + private static final int DRAG_HANDLE_BOUNCE_REPEAT_COUNT = 2; + private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; @@ -123,6 +131,7 @@ public class ScrimView extends View implements Insettable, O protected float mDragHandleOffset; private final Rect mDragHandleBounds; private final RectF mHitRect = new RectF(); + private ObjectAnimator mDragHandleAnim; private final MultiValueAlpha mMultiValueAlpha; @@ -212,6 +221,7 @@ public class ScrimView extends View implements Insettable, O public void setProgress(float progress) { if (mProgress != progress) { mProgress = progress; + stopDragHandleEducationAnim(); updateColors(); updateDragHandleAlpha(); invalidate(); @@ -259,46 +269,77 @@ public class ScrimView extends View implements Insettable, O @Override public boolean onTouchEvent(MotionEvent event) { - boolean value = super.onTouchEvent(event); - if (!value && mDragHandle != null && event.getAction() == ACTION_DOWN - && mDragHandle.getAlpha() == 255 - && mHitRect.contains(event.getX(), event.getY())) { - - final Drawable drawable = mDragHandle; - mDragHandle = null; - - Rect bounds = new Rect(mDragHandleBounds); - bounds.offset(0, -(int) mDragHandleOffset); - drawable.setBounds(bounds); - - Rect topBounds = new Rect(bounds); - topBounds.offset(0, -bounds.height() / 2); - - Rect invalidateRegion = new Rect(bounds); - invalidateRegion.top = topBounds.top; - - Keyframe frameTop = Keyframe.ofObject(0.6f, topBounds); - frameTop.setInterpolator(DEACCEL); - Keyframe frameBot = Keyframe.ofObject(1, bounds); - frameBot.setInterpolator(ACCEL); - PropertyValuesHolder holder = PropertyValuesHolder .ofKeyframe("bounds", - Keyframe.ofObject(0, bounds), frameTop, frameBot); - holder.setEvaluator(new RectEvaluator()); - - ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - getOverlay().remove(drawable); - updateDragHandleVisibility(drawable); + boolean superHandledTouch = super.onTouchEvent(event); + if (event.getAction() == ACTION_DOWN) { + if (!superHandledTouch && mHitRect.contains(event.getX(), event.getY())) { + if (startDragHandleEducationAnim()) { + return true; } - }); - anim.addUpdateListener((v) -> invalidate(invalidateRegion)); - getOverlay().add(drawable); - anim.start(); - return true; + } + stopDragHandleEducationAnim(); + } + return superHandledTouch; + } + + /** + * Animates the drag handle to demonstrate how to get to all apps. + * @return Whether the animation was started (false if drag handle is invisible). + */ + public boolean startDragHandleEducationAnim() { + stopDragHandleEducationAnim(); + + if (mDragHandle == null || mDragHandle.getAlpha() != 255) { + return false; + } + + final Drawable drawable = mDragHandle; + mDragHandle = null; + + Rect bounds = new Rect(mDragHandleBounds); + bounds.offset(0, -(int) mDragHandleOffset); + drawable.setBounds(bounds); + + Rect topBounds = new Rect(bounds); + topBounds.offset(0, -bounds.height()); + + Rect invalidateRegion = new Rect(bounds); + invalidateRegion.top = topBounds.top; + + final float progressToReachTop = 0.6f; + Keyframe frameTop = Keyframe.ofObject(progressToReachTop, topBounds); + frameTop.setInterpolator(DEACCEL); + Keyframe frameBot = Keyframe.ofObject(1, bounds); + frameBot.setInterpolator(ACCEL_DEACCEL); + PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("bounds", + Keyframe.ofObject(0, bounds), frameTop, frameBot); + holder.setEvaluator(new RectEvaluator()); + + mDragHandleAnim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder); + long totalBounceDuration = DRAG_HANDLE_BOUNCE_DURATION_MS + DRAG_HANDLE_BOUNCE_DELAY_MS; + // The bounce finishes by this progress, the rest of the duration just delays next bounce. + float delayStartProgress = 1f - (float) DRAG_HANDLE_BOUNCE_DELAY_MS / totalBounceDuration; + mDragHandleAnim.addUpdateListener((v) -> invalidate(invalidateRegion)); + mDragHandleAnim.setDuration(totalBounceDuration); + mDragHandleAnim.setInterpolator(clampToProgress(LINEAR, 0, delayStartProgress)); + mDragHandleAnim.setRepeatCount(DRAG_HANDLE_BOUNCE_REPEAT_COUNT); + getOverlay().add(drawable); + + mDragHandleAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDragHandleAnim = null; + getOverlay().remove(drawable); + updateDragHandleVisibility(drawable); + } + }); + mDragHandleAnim.start(); + return true; + } + + private void stopDragHandleEducationAnim() { + if (mDragHandleAnim != null) { + mDragHandleAnim.end(); } - return value; } protected void updateDragHandleBounds() {