Add all apps education bounce animation
- Update existing arrow bounce animation to repeat 3 times,
and play it when swiping up from nav bar on first home
screen as well as when tapping the arrow.
Bug: 151768994
Change-Id: Ib120764fdeab6cd932018b6fed8b1093dda20641
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 e62de18..98eb29a 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.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 @@
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 5b9f676..fb8bd45 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1193,7 +1193,7 @@
// 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 @@
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 8bc0242..190ec16 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -125,7 +125,7 @@
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 00128eb..68b0706 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -212,9 +212,9 @@
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 290dbb6..43f30f1 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -17,6 +17,7 @@
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 @@
@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 adad097..39e1eac 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -22,8 +22,10 @@
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 @@
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 @@
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 void setProgress(float progress) {
if (mProgress != progress) {
mProgress = progress;
+ stopDragHandleEducationAnim();
updateColors();
updateDragHandleAlpha();
invalidate();
@@ -259,46 +269,77 @@
@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 value;
+ 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();
+ }
}
protected void updateDragHandleBounds() {