summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/launcher3/Launcher.java1
-rw-r--r--src/com/android/launcher3/PagedView.java2
-rw-r--r--src/com/android/launcher3/PinchAnimationManager.java285
-rw-r--r--src/com/android/launcher3/PinchThresholdManager.java77
-rw-r--r--src/com/android/launcher3/PinchToOverviewListener.java179
-rw-r--r--src/com/android/launcher3/Workspace.java12
-rw-r--r--src/com/android/launcher3/dragndrop/DragLayer.java17
7 files changed, 573 insertions, 0 deletions
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2d52341a7..fc828da7b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -3136,6 +3136,7 @@ public class Launcher extends Activity
getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+ @Override
public boolean onLongClick(View v) {
if (!isDraggingEnabled()) return false;
if (isWorkspaceLocked()) return false;
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index bc15338eb..fc8bb4507 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1445,6 +1445,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc
mLastMotionXRemainder = 0;
onScrollInteractionBegin();
pageBeginMoving();
+ // Stop listening for things like pinches.
+ requestDisallowInterceptTouchEvent(true);
}
}
diff --git a/src/com/android/launcher3/PinchAnimationManager.java b/src/com/android/launcher3/PinchAnimationManager.java
new file mode 100644
index 000000000..3302cb652
--- /dev/null
+++ b/src/com/android/launcher3/PinchAnimationManager.java
@@ -0,0 +1,285 @@
+package com.android.launcher3;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.View;
+
+import static com.android.launcher3.Workspace.State.NORMAL;
+import static com.android.launcher3.Workspace.State.OVERVIEW;
+
+/**
+ * Manages the animations that play as the user pinches to/from overview mode.
+ *
+ * It will look like this pinching in:
+ * - Workspace scales down
+ * - At some threshold 1, hotseat and QSB fade out (full animation)
+ * - At a later threshold 2, panel buttons fade in and scrim fades in
+ * - At a final threshold 3, snap to overview
+ *
+ * Pinching out:
+ * - Workspace scales up
+ * - At threshold 1, panel buttons fade out
+ * - At threshold 2, hotseat and QSB fade in and scrim fades out
+ * - At threshold 3, snap to workspace
+ *
+ * @see PinchToOverviewListener
+ * @see PinchThresholdManager
+ */
+public class PinchAnimationManager {
+ private static final String TAG = "PinchAnimationManager";
+
+ private static final int THRESHOLD_ANIM_DURATION = 150;
+
+ private Launcher mLauncher;
+ private Workspace mWorkspace;
+
+ private float mOverviewScale;
+ private float mOverviewTranslationY;
+ private int mNormalOverviewTransitionDuration;
+ private final int[] mVisiblePageRange = new int[2];
+ private boolean mIsAnimating;
+
+ // Animators
+ private Animator mShowPageIndicatorAnimator;
+ private Animator mShowHotseatAnimator;
+ private Animator mShowOverviewPanelButtonsAnimator;
+ private Animator mShowScrimAnimator;
+ private Animator mHidePageIndicatorAnimator;
+ private Animator mHideHotseatAnimator;
+ private Animator mHideOverviewPanelButtonsAnimator;
+ private Animator mHideScrimAnimator;
+
+ public PinchAnimationManager(Launcher launcher) {
+ mLauncher = launcher;
+ mWorkspace = launcher.mWorkspace;
+
+ mOverviewScale = mWorkspace.getOverviewModeShrinkFactor();
+ mOverviewTranslationY = mWorkspace.getOverviewModeTranslationY();
+ mNormalOverviewTransitionDuration = mWorkspace.getStateTransitionAnimation()
+ .mOverviewTransitionTime;
+
+ initializeAnimators();
+ }
+
+ private void initializeAnimators() {
+ mShowPageIndicatorAnimator = new LauncherViewPropertyAnimator(
+ mWorkspace.getPageIndicator()).alpha(1f).withLayer();
+ mShowPageIndicatorAnimator.setInterpolator(null);
+
+ mShowHotseatAnimator = new LauncherViewPropertyAnimator(mLauncher.getHotseat())
+ .alpha(1f).withLayer();
+ mShowHotseatAnimator.setInterpolator(null);
+
+ mShowOverviewPanelButtonsAnimator = new LauncherViewPropertyAnimator(
+ mLauncher.getOverviewPanel()).alpha(1f).withLayer();
+ mShowOverviewPanelButtonsAnimator.setInterpolator(null);
+
+ mShowScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
+ mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha);
+ mShowScrimAnimator.setInterpolator(null);
+
+ mHidePageIndicatorAnimator = new LauncherViewPropertyAnimator(
+ mWorkspace.getPageIndicator()).alpha(0f).withLayer();
+ mHidePageIndicatorAnimator.setInterpolator(null);
+ mHidePageIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mWorkspace.getPageIndicator() != null) {
+ mWorkspace.getPageIndicator().setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+
+ mHideHotseatAnimator = new LauncherViewPropertyAnimator(mLauncher.getHotseat())
+ .alpha(0f).withLayer();
+ mHideHotseatAnimator.setInterpolator(null);
+ mHideHotseatAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getHotseat().setVisibility(View.INVISIBLE);
+ }
+ });
+
+ mHideOverviewPanelButtonsAnimator = new LauncherViewPropertyAnimator(
+ mLauncher.getOverviewPanel()).alpha(0f).withLayer();
+ mHideOverviewPanelButtonsAnimator.setInterpolator(null);
+ mHideOverviewPanelButtonsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getOverviewPanel().setVisibility(View.INVISIBLE);
+ }
+ });
+
+ mHideScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha", 0f);
+ mHideScrimAnimator.setInterpolator(null);
+ }
+
+ public int getNormalOverviewTransitionDuration() {
+ return mNormalOverviewTransitionDuration;
+ }
+
+ /**
+ * Interpolate from {@param currentProgress} to {@param toProgress}, calling
+ * {@link #setAnimationProgress(float)} throughout the duration. If duration is -1,
+ * the default overview transition duration is used.
+ */
+ public void animateToProgress(float currentProgress, float toProgress, int duration,
+ final PinchThresholdManager thresholdManager) {
+ if (duration == -1) {
+ duration = mNormalOverviewTransitionDuration;
+ }
+ ValueAnimator animator = ValueAnimator.ofFloat(currentProgress, toProgress);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float pinchProgress = (Float) animation.getAnimatedValue();
+ setAnimationProgress(pinchProgress);
+ thresholdManager.updateAndAnimatePassedThreshold(pinchProgress,
+ PinchAnimationManager.this);
+ }
+ }
+ );
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimating = false;
+ thresholdManager.reset();
+ }
+ });
+ animator.setDuration(duration).start();
+ mIsAnimating = true;
+ }
+
+ public boolean isAnimating() {
+ return mIsAnimating;
+ }
+
+ /**
+ * Animates to the specified progress. This should be called repeatedly throughout the pinch
+ * gesture to run animations that interpolate throughout the gesture.
+ * @param interpolatedProgress The progress from 0 to 1, where 0 is overview and 1 is workspace.
+ */
+ public void setAnimationProgress(float interpolatedProgress) {
+ float interpolatedScale = interpolatedProgress * (1f - mOverviewScale) + mOverviewScale;
+ float interpolatedTranslationY = (1f - interpolatedProgress) * mOverviewTranslationY;
+ mWorkspace.setScaleX(interpolatedScale);
+ mWorkspace.setScaleY(interpolatedScale);
+ mWorkspace.setTranslationY(interpolatedTranslationY);
+ setOverviewPanelsAlpha(1f - interpolatedProgress, 0);
+
+ // Make sure adjacent pages, except custom content page, are visible while scaling.
+ mWorkspace.setCustomContentVisibility(View.INVISIBLE);
+ mWorkspace.invalidate();
+ }
+
+ /**
+ * Animates certain properties based on which threshold was passed, and in what direction. The
+ * starting state must also be taken into account because the thresholds mean different things
+ * when going from workspace to overview and vice versa.
+ * @param threshold One of {@link PinchThresholdManager#THRESHOLD_ONE},
+ * {@link PinchThresholdManager#THRESHOLD_TWO}, or
+ * {@link PinchThresholdManager#THRESHOLD_THREE}
+ * @param startState {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
+ * @param goingTowards {@link Workspace.State#NORMAL} or {@link Workspace.State#OVERVIEW}.
+ * Note that this doesn't have to be the opposite of startState;
+ */
+ public void animateThreshold(float threshold, Workspace.State startState,
+ Workspace.State goingTowards) {
+ if (threshold == PinchThresholdManager.THRESHOLD_ONE) {
+ if (startState == OVERVIEW) {
+ animateOverviewPanelButtons(goingTowards == OVERVIEW);
+ } else if (startState == NORMAL) {
+ animateHotseatAndPageIndicator(goingTowards == NORMAL);
+ animateQsb(goingTowards == NORMAL);
+ }
+ } else if (threshold == PinchThresholdManager.THRESHOLD_TWO) {
+ if (startState == OVERVIEW) {
+ animateHotseatAndPageIndicator(goingTowards == NORMAL);
+ animateQsb(goingTowards == NORMAL);
+ animateScrim(goingTowards == OVERVIEW);
+ } else if (startState == NORMAL) {
+ animateOverviewPanelButtons(goingTowards == OVERVIEW);
+ animateScrim(goingTowards == OVERVIEW);
+ }
+ } else if (threshold == PinchThresholdManager.THRESHOLD_THREE) {
+ // Passing threshold 3 ends the pinch and snaps to the new state.
+ if (startState == OVERVIEW && goingTowards == NORMAL) {
+ mLauncher.showWorkspace(true);
+ mWorkspace.snapToPage(mWorkspace.getPageNearestToCenterOfScreen());
+ } else if (startState == NORMAL && goingTowards == OVERVIEW) {
+ mLauncher.showOverviewMode(true);
+ }
+ } else {
+ Log.e(TAG, "Received unknown threshold to animate: " + threshold);
+ }
+ }
+
+ private void setOverviewPanelsAlpha(float alpha, int duration) {
+ mWorkspace.getVisiblePages(mVisiblePageRange);
+ for (int i = mVisiblePageRange[0]; i <= mVisiblePageRange[1]; i++) {
+ View page = mWorkspace.getPageAt(i);
+ if (!mWorkspace.shouldDrawChild(page)) {
+ continue;
+ }
+ if (duration == 0) {
+ ((CellLayout) page).setBackgroundAlpha(alpha);
+ } else {
+ ObjectAnimator.ofFloat(page, "backgroundAlpha", alpha)
+ .setDuration(duration).start();
+ }
+ }
+ }
+
+ private void animateHotseatAndPageIndicator(boolean show) {
+ if (show) {
+ mLauncher.getHotseat().setVisibility(View.VISIBLE);
+ mShowHotseatAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ if (mWorkspace.getPageIndicator() != null) {
+ // There aren't page indicators in landscape mode on phones, hence the null check.
+ mWorkspace.getPageIndicator().setVisibility(View.VISIBLE);
+ mShowPageIndicatorAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ }
+ } else {
+ mHideHotseatAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ if (mWorkspace.getPageIndicator() != null) {
+ // There aren't page indicators in landscape mode on phones, hence the null check.
+ mHidePageIndicatorAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ }
+ }
+ }
+
+ private void animateQsb(boolean show) {
+ SearchDropTargetBar.State searchBarState = show ? SearchDropTargetBar.State.SEARCH_BAR
+ : SearchDropTargetBar.State.INVISIBLE;
+ mLauncher.getSearchDropTargetBar().animateToState(searchBarState, THRESHOLD_ANIM_DURATION);
+ }
+
+ private void animateOverviewPanelButtons(boolean show) {
+ if (show) {
+ mLauncher.getOverviewPanel().setVisibility(View.VISIBLE);
+ mShowOverviewPanelButtonsAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ } else {
+ mHideOverviewPanelButtonsAnimator.setDuration(THRESHOLD_ANIM_DURATION).start();
+ }
+ }
+
+ private void animateScrim(boolean show) {
+ // We reninitialize the animators here so that they have the correct start values.
+ if (show) {
+ mShowScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
+ mWorkspace.getStateTransitionAnimation().mWorkspaceScrimAlpha);
+ mShowScrimAnimator.setInterpolator(null);
+ mShowScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
+ } else {
+ mHideScrimAnimator.setupStartValues();
+ mHideScrimAnimator = ObjectAnimator.ofFloat(mLauncher.getDragLayer(), "backgroundAlpha",
+ 0f);
+ mHideScrimAnimator.setInterpolator(null);
+ mHideScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
+ mHideScrimAnimator.setDuration(mNormalOverviewTransitionDuration).start();
+ }
+ }
+}
diff --git a/src/com/android/launcher3/PinchThresholdManager.java b/src/com/android/launcher3/PinchThresholdManager.java
new file mode 100644
index 000000000..d79a63174
--- /dev/null
+++ b/src/com/android/launcher3/PinchThresholdManager.java
@@ -0,0 +1,77 @@
+package com.android.launcher3;
+
+/**
+ * Keeps track of when thresholds are passed during a pinch gesture,
+ * used to inform {@link PinchAnimationManager} throughout.
+ *
+ * @see PinchToOverviewListener
+ * @see PinchAnimationManager
+ */
+public class PinchThresholdManager {
+ public static final float THRESHOLD_ZERO = 0.0f;
+ public static final float THRESHOLD_ONE = 0.40f;
+ public static final float THRESHOLD_TWO = 0.70f;
+ public static final float THRESHOLD_THREE = 0.95f;
+
+ private Workspace mWorkspace;
+
+ private float mPassedThreshold = THRESHOLD_ZERO;
+
+ public PinchThresholdManager(Workspace workspace) {
+ mWorkspace = workspace;
+ }
+
+ /**
+ * Uses the pinch progress to determine whether a threshold has been passed,
+ * and asks the {@param animationManager} to animate if so.
+ * @param progress From 0 to 1, where 0 is overview and 1 is workspace.
+ * @param animationManager Animates the threshold change if one is passed.
+ * @return The last passed threshold, one of
+ * {@link PinchThresholdManager#THRESHOLD_ZERO},
+ * {@link PinchThresholdManager#THRESHOLD_ONE},
+ * {@link PinchThresholdManager#THRESHOLD_TWO}, or
+ * {@link PinchThresholdManager#THRESHOLD_THREE}
+ */
+ public float updateAndAnimatePassedThreshold(float progress,
+ PinchAnimationManager animationManager) {
+ if (!mWorkspace.isInOverviewMode()) {
+ // Invert the progress, because going from workspace to overview is 1 to 0.
+ progress = 1f - progress;
+ }
+
+ float previousPassedThreshold = mPassedThreshold;
+
+ if (progress < THRESHOLD_ONE) {
+ mPassedThreshold = THRESHOLD_ZERO;
+ } else if (progress < THRESHOLD_TWO) {
+ mPassedThreshold = THRESHOLD_ONE;
+ } else if (progress < THRESHOLD_THREE) {
+ mPassedThreshold = THRESHOLD_TWO;
+ } else {
+ mPassedThreshold = THRESHOLD_THREE;
+ }
+
+ if (mPassedThreshold != previousPassedThreshold) {
+ Workspace.State fromState = mWorkspace.isInOverviewMode() ? Workspace.State.OVERVIEW
+ : Workspace.State.NORMAL;
+ Workspace.State toState = mWorkspace.isInOverviewMode() ? Workspace.State.NORMAL
+ : Workspace.State.OVERVIEW;
+ float thresholdToAnimate = mPassedThreshold;
+ if (mPassedThreshold < previousPassedThreshold) {
+ // User reversed pinch, so heading back to the state that they started from.
+ toState = fromState;
+ thresholdToAnimate = previousPassedThreshold;
+ }
+ animationManager.animateThreshold(thresholdToAnimate, fromState, toState);
+ }
+ return mPassedThreshold;
+ }
+
+ public float getPassedThreshold() {
+ return mPassedThreshold;
+ }
+
+ public void reset() {
+ mPassedThreshold = THRESHOLD_ZERO;
+ }
+}
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
new file mode 100644
index 000000000..ef4748528
--- /dev/null
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -0,0 +1,179 @@
+package com.android.launcher3;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+/**
+ * Detects pinches and animates the Workspace to/from overview mode.
+ *
+ * Usage: Pass MotionEvents to onInterceptTouchEvent() and onTouchEvent(). This class will handle
+ * the pinch detection, and use {@link PinchAnimationManager} to handle the animations.
+ *
+ * @see PinchThresholdManager
+ * @see PinchAnimationManager
+ */
+public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ private static final float OVERVIEW_PROGRESS = 0f;
+ private static final float WORKSPACE_PROGRESS = 1f;
+
+ private ScaleGestureDetector mPinchDetector;
+ private Launcher mLauncher;
+ private Workspace mWorkspace = null;
+ private boolean mPinchStarted = false;
+ private float mPreviousProgress;
+ private float mProgressDelta;
+ private long mPreviousTimeMillis;
+ private long mTimeDelta;
+ private boolean mPinchCanceled = false;
+ private TimeInterpolator mInterpolator;
+
+ private PinchThresholdManager mThresholdManager;
+ private PinchAnimationManager mAnimationManager;
+
+ public PinchToOverviewListener(Launcher launcher) {
+ mLauncher = launcher;
+ mPinchDetector = new ScaleGestureDetector((Context) mLauncher, this);
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mPinchDetector.onTouchEvent(ev);
+ return mPinchStarted;
+ }
+
+ public void onTouchEvent(MotionEvent ev) {
+ if (mPinchStarted) {
+ if (ev.getPointerCount() > 2) {
+ // Using more than two fingers causes weird behavior, so just cancel the pinch.
+ cancelPinch(mPreviousProgress, -1);
+ } else {
+ mPinchDetector.onTouchEvent(ev);
+ }
+ }
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (mLauncher.mState != Launcher.State.WORKSPACE || mLauncher.isOnCustomContent()) {
+ // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+ return false;
+ }
+ if (mAnimationManager != null && mAnimationManager.isAnimating()) {
+ // Don't listen for the pinch gesture if we are already animating from a previous one.
+ return false;
+ }
+ if (mWorkspace == null) {
+ mWorkspace = mLauncher.mWorkspace;
+ mThresholdManager = new PinchThresholdManager(mWorkspace);
+ mAnimationManager = new PinchAnimationManager(mLauncher);
+ }
+ if (mWorkspace.isSwitchingState() || mWorkspace.mScrollInteractionBegan) {
+ // Don't listen to pinches occurring while switching state, as it will cause a jump
+ // once the state switching animation is complete.
+ return false;
+ }
+
+ mPreviousProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
+ mPreviousTimeMillis = System.currentTimeMillis();
+ mInterpolator = mWorkspace.isInOverviewMode() ? new LogDecelerateInterpolator(100, 0)
+ : new LogAccelerateInterpolator(100, 0);
+ mPinchStarted = true;
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ super.onScaleEnd(detector);
+
+ float passedThreshold = mThresholdManager.getPassedThreshold();
+ // If we are going towards overview, mPreviousProgress is how much further we need to
+ // go, since it is going from 1 to 0. If we are going to workspace, we want
+ // 1 - mPreviousProgress.
+ float remainingProgress = mPreviousProgress;
+ if (mWorkspace.isInOverviewMode() || passedThreshold < PinchThresholdManager.THRESHOLD_ONE) {
+ remainingProgress = 1f - mPreviousProgress;
+ }
+ int duration = computeDuration(remainingProgress, mProgressDelta, mTimeDelta);
+
+ if (passedThreshold < PinchThresholdManager.THRESHOLD_ONE) {
+ cancelPinch(mPreviousProgress, duration);
+ } else if (passedThreshold < PinchThresholdManager.THRESHOLD_THREE) {
+ float toProgress = mWorkspace.isInOverviewMode() ?
+ WORKSPACE_PROGRESS : OVERVIEW_PROGRESS;
+ mAnimationManager.animateToProgress(mPreviousProgress, toProgress, duration,
+ mThresholdManager);
+ } else {
+ mThresholdManager.reset();
+ }
+ mPinchStarted = false;
+ mPinchCanceled = false;
+ }
+
+ /**
+ * Compute the amount of time required to complete the transition based on the current pinch
+ * speed. If this time is too long, instead return the normal duration, ignoring the speed.
+ */
+ private int computeDuration(float remainingProgress, float progressDelta, long timeDelta) {
+ float progressSpeed = Math.abs(progressDelta) / timeDelta;
+ int remainingMillis = (int) (remainingProgress / progressSpeed);
+ return Math.min(remainingMillis, mAnimationManager.getNormalOverviewTransitionDuration());
+ }
+
+ /**
+ * Cancels the current pinch, returning back to where the pinch started (either workspace or
+ * overview). If duration is -1, the default overview transition duration is used.
+ */
+ private void cancelPinch(float currentProgress, int duration) {
+ if (mPinchCanceled) return;
+ mPinchCanceled = true;
+ float toProgress = mWorkspace.isInOverviewMode() ? OVERVIEW_PROGRESS : WORKSPACE_PROGRESS;
+ mAnimationManager.animateToProgress(currentProgress, toProgress, duration,
+ mThresholdManager);
+ mPinchStarted = false;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (mThresholdManager.getPassedThreshold() == PinchThresholdManager.THRESHOLD_THREE) {
+ // We completed the pinch, so stop listening to further movement until user lets go.
+ return true;
+ }
+ if (mLauncher.getDragController().isDragging()) {
+ mLauncher.getDragController().cancelDrag();
+ }
+
+ float pinchDist = detector.getCurrentSpan() - detector.getPreviousSpan();
+ if (pinchDist < 0 && mWorkspace.isInOverviewMode() ||
+ pinchDist > 0 && !mWorkspace.isInOverviewMode()) {
+ // Pinching the wrong way, so ignore.
+ return false;
+ }
+ // Pinch distance must equal the workspace width before switching states.
+ int pinchDistanceToCompleteTransition = mWorkspace.getWidth();
+ float overviewScale = mWorkspace.getOverviewModeShrinkFactor();
+ float initialWorkspaceScale = mWorkspace.isInOverviewMode() ? overviewScale : 1f;
+ float pinchScale = initialWorkspaceScale + pinchDist / pinchDistanceToCompleteTransition;
+ // Bound the scale between the overview scale and the normal workspace scale (1f).
+ pinchScale = Math.max(overviewScale, Math.min(pinchScale, 1f));
+ // Progress ranges from 0 to 1, where 0 corresponds to the overview scale and 1
+ // corresponds to the normal workspace scale (1f).
+ float progress = (pinchScale - overviewScale) / (1f - overviewScale);
+ float interpolatedProgress = mInterpolator.getInterpolation(progress);
+
+ mAnimationManager.setAnimationProgress(interpolatedProgress);
+ float passedThreshold = mThresholdManager.updateAndAnimatePassedThreshold(
+ interpolatedProgress, mAnimationManager);
+ if (passedThreshold == PinchThresholdManager.THRESHOLD_THREE) {
+ return true;
+ }
+
+ mProgressDelta = interpolatedProgress - mPreviousProgress;
+ mPreviousProgress = interpolatedProgress;
+ mTimeDelta = System.currentTimeMillis() - mPreviousTimeMillis;
+ mPreviousTimeMillis = System.currentTimeMillis();
+ return false;
+ }
+
+
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0f8d834da..a74fa0dbc 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1900,6 +1900,10 @@ public class Workspace extends PagedView
return -(workspaceHeight - scaledHeight) / 2;
}
+ float getOverviewModeShrinkFactor() {
+ return mOverviewModeShrinkFactor;
+ }
+
/**
* Sets the current workspace {@link State}, returning an animation transitioning the workspace
* to that new state.
@@ -1996,6 +2000,10 @@ public class Workspace extends PagedView
void updateCustomContentVisibility() {
int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
+ setCustomContentVisibility(visibility);
+ }
+
+ void setCustomContentVisibility(int visibility) {
if (hasCustomContent()) {
mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
}
@@ -3526,6 +3534,10 @@ public class Workspace extends PagedView
}
}
+ public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
+ return mStateTransitionAnimation;
+ }
+
/**
* Return the current {@link CellLayout}, correctly picking the destination
* screen while a scroll is in progress.
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f16a56c8b..647ec5e9f 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -47,6 +47,7 @@ import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.PinchToOverviewListener;
import com.android.launcher3.R;
import com.android.launcher3.SearchDropTargetBar;
import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -110,6 +111,8 @@ public class DragLayer extends InsettableFrameLayout {
private Drawable mLeftHoverDrawableActive;
private Drawable mRightHoverDrawableActive;
+ // Related to pinch-to-go-to-overview gesture.
+ private PinchToOverviewListener mPinchListener;
/**
* Used to create a new DragLayer from XML.
*
@@ -134,6 +137,8 @@ public class DragLayer extends InsettableFrameLayout {
public void setup(Launcher launcher, DragController controller) {
mLauncher = launcher;
mDragController = controller;
+
+ mPinchListener = new PinchToOverviewListener(mLauncher);
}
@Override
@@ -241,6 +246,14 @@ public class DragLayer extends InsettableFrameLayout {
mTouchCompleteListener = null;
}
clearAllResizeFrames();
+
+ Folder currentFolder = mLauncher.getWorkspace().getOpenFolder();
+ if (currentFolder == null) {
+ if (mPinchListener.onInterceptTouchEvent(ev)) {
+ // Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
+ return true;
+ }
+ }
return mDragController.onInterceptTouchEvent(ev);
}
@@ -354,6 +367,10 @@ public class DragLayer extends InsettableFrameLayout {
int x = (int) ev.getX();
int y = (int) ev.getY();
+ // This is only reached if a pinch was started from onInterceptTouchEvent();
+ // this continues sending events for it.
+ mPinchListener.onTouchEvent(ev);
+
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, false)) {
return true;