summaryrefslogtreecommitdiffstats
path: root/quickstep
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2018-01-12 14:17:30 -0800
committerSunny Goyal <sunnygoyal@google.com>2018-01-16 10:31:36 -0800
commit05a3bbdef8c328370a51e045db12d67e62956b3f (patch)
tree4f2c04f988d272f2de022f959e5c4fad4970a2d7 /quickstep
parent9f082604b0b2e9bc5fe857e418739252e7f3e81d (diff)
downloadandroid_packages_apps_Trebuchet-05a3bbdef8c328370a51e045db12d67e62956b3f.tar.gz
android_packages_apps_Trebuchet-05a3bbdef8c328370a51e045db12d67e62956b3f.tar.bz2
android_packages_apps_Trebuchet-05a3bbdef8c328370a51e045db12d67e62956b3f.zip
Adding swipe gestures in overview screen
> When on home time, swiping up goes to all_apps, and swiping down goes to normal > When on a recents tile, swiping up the tile dismisses it, swiping down launches it > When on a recents tile, swiping up on the hotseat opens allApps. Change-Id: I59f8c02f5c5d9cb88c0585a083fbc33d33b1c806
Diffstat (limited to 'quickstep')
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java323
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/UiFactory.java4
-rw-r--r--quickstep/src/com/android/quickstep/TaskView.java133
3 files changed, 329 insertions, 131 deletions
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
new file mode 100644
index 000000000..335077acd
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2018 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.launcher3.uioverrides;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
+
+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.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
+/**
+ * Touch controller for swipe interaction in Overview state
+ */
+public class OverviewSwipeController extends AnimatorListenerAdapter
+ implements TouchController, SwipeDetector.Listener {
+
+ private static final String TAG = "OverviewSwipeController";
+
+ private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;
+ private static final int SINGLE_FRAME_MS = 16;
+
+ // Progress after which the transition is assumed to be a success in case user does not fling
+ private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+ private final Launcher mLauncher;
+ private final SwipeDetector mDetector;
+ private final RecentsView mRecentsView;
+ private final int[] mTempCords = new int[2];
+
+ private AnimatorPlaybackController mCurrentAnimation;
+ private boolean mCurrentAnimationIsGoingUp;
+
+ private boolean mNoIntercept;
+ private boolean mSwipeDownEnabled;
+
+ private float mDisplacementShift;
+ private float mProgressMultiplier;
+ private float mEndDisplacement;
+
+ private TaskView mTaskBeingDragged;
+
+ public OverviewSwipeController(Launcher launcher) {
+ mLauncher = launcher;
+ mRecentsView = launcher.getOverviewPanel();
+ mDetector = new SwipeDetector(launcher, this, SwipeDetector.VERTICAL);
+ }
+
+ private boolean canInterceptTouch() {
+ if (mCurrentAnimation != null) {
+ // If we are already animating from a previous state, we can intercept.
+ return true;
+ }
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ return false;
+ }
+ return mLauncher.isInState(OVERVIEW);
+ }
+
+ private boolean isEventOverHotseat(MotionEvent ev) {
+ if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+ return ev.getY() >
+ mLauncher.getDragLayer().getHeight() * OVERVIEW.getVerticalProgress(mLauncher);
+ } else {
+ return mLauncher.getDragLayer().isEventOverHotseat(ev);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+ Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+ mDetector.finishedScrolling();
+ mCurrentAnimation = null;
+ }
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = !canInterceptTouch();
+ if (mNoIntercept) {
+ return false;
+ }
+
+ // Now figure out which direction scroll events the controller will start
+ // calling the callbacks.
+ final int directionsToDetectScroll;
+ boolean ignoreSlopWhenSettling = false;
+
+ if (mCurrentAnimation != null) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ ignoreSlopWhenSettling = true;
+ } else {
+ mTaskBeingDragged = null;
+ mSwipeDownEnabled = true;
+
+ int currentPage = mRecentsView.getCurrentPage();
+ if (currentPage == 0) {
+ // User is on home tile
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ } else {
+ View view = mRecentsView.getChildAt(currentPage);
+ if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
+ view instanceof TaskView) {
+ // The tile can be dragged down to open the task.
+ mTaskBeingDragged = (TaskView) view;
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ } else if (isEventOverHotseat(ev)) {
+ // The hotseat is being dragged
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ mSwipeDownEnabled = false;
+ } else {
+ mNoIntercept = true;
+ return false;
+ }
+ }
+ }
+
+ mDetector.setDetectableScrollConditions(
+ directionsToDetectScroll, ignoreSlopWhenSettling);
+ }
+
+ if (mNoIntercept) {
+ return false;
+ }
+
+ onControllerTouchEvent(ev);
+ return mDetector.isDraggingOrSettling();
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return mDetector.onTouchEvent(ev);
+ }
+
+ private void reinitAnimationController(boolean goingUp) {
+ if (!goingUp && !mSwipeDownEnabled) {
+ goingUp = true;
+ }
+ if (mCurrentAnimation != null && mCurrentAnimationIsGoingUp == goingUp) {
+ // No need to init
+ return;
+ }
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.setPlayFraction(0);
+ }
+ mCurrentAnimationIsGoingUp = goingUp;
+ float range = mLauncher.getAllAppsController().getShiftRange();
+ long maxDuration = (long) (2 * range);
+ DragLayer dl = mLauncher.getDragLayer();
+
+ if (mTaskBeingDragged == null) {
+ // User is either going to all apps or home
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(goingUp ? ALL_APPS : NORMAL, maxDuration);
+ if (goingUp) {
+ mEndDisplacement = -range;
+ } else {
+ View ws = mLauncher.getWorkspace();
+ mTempCords[1] = ws.getHeight() - ws.getPaddingBottom();
+ dl.getDescendantCoordRelativeToSelf(ws, mTempCords);
+
+ float distance = mTempCords[1];
+ if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+ mTempCords[1] = 0;
+ dl.getDescendantCoordRelativeToSelf(mLauncher.getHotseat(), mTempCords);
+ distance = mTempCords[1] - distance;
+ } else {
+ distance = dl.getHeight() - distance;
+ }
+
+ mEndDisplacement = distance;
+ }
+ } else {
+ if (goingUp) {
+ AnimatorSet anim = new AnimatorSet();
+ ObjectAnimator translate = ObjectAnimator.ofFloat(
+ mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
+ translate.setInterpolator(LINEAR);
+ translate.setDuration(maxDuration);
+ anim.play(translate);
+
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
+ alpha.setInterpolator(DEACCEL_1_5);
+ alpha.setDuration(maxDuration);
+ anim.play(alpha);
+ mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+ mEndDisplacement = -mTaskBeingDragged.getBottom();
+ } else {
+ AnimatorSet anim = new AnimatorSet();
+ // TODO: Setup a zoom animation
+ mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
+
+ mTempCords[1] = mTaskBeingDragged.getHeight();
+ dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
+ mEndDisplacement = dl.getHeight() - mTempCords[1];
+ }
+ }
+
+ mCurrentAnimation.getTarget().addListener(this);
+ mCurrentAnimation.dispatchOnStart();
+ mProgressMultiplier = 1 / mEndDisplacement;
+ }
+
+ @Override
+ public void onDragStart(boolean start) {
+ if (mCurrentAnimation == null) {
+ reinitAnimationController(mDetector.wasInitialTouchPositive());
+ mDisplacementShift = 0;
+ } else {
+ mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
+ mCurrentAnimation.pause();
+ }
+ }
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ float totalDisplacement = displacement + mDisplacementShift;
+ boolean isGoingUp =
+ totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+ if (isGoingUp != mCurrentAnimationIsGoingUp) {
+ reinitAnimationController(isGoingUp);
+ }
+ mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ final boolean goingToEnd;
+
+ if (fling) {
+ boolean goingUp = velocity < 0;
+ if (!goingUp && !mSwipeDownEnabled) {
+ goingToEnd = false;
+ } else if (goingUp != mCurrentAnimationIsGoingUp) {
+ // In case the fling is in opposite direction, make sure if is close enough
+ // from the start position
+ if (mCurrentAnimation.getProgressFraction()
+ >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
+ // Not allowed
+ goingToEnd = false;
+ } else {
+ reinitAnimationController(goingUp);
+ goingToEnd = true;
+ }
+ } else {
+ goingToEnd = true;
+ }
+ } else {
+ goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
+ }
+
+ float progress = mCurrentAnimation.getProgressFraction();
+ long animationDuration = SwipeDetector.calculateDuration(
+ velocity, goingToEnd ? (1 - progress) : progress);
+
+ float nextFrameProgress = Utilities.boundToRange(
+ progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+
+
+ mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+
+ ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+ anim.setFloatValues(nextFrameProgress, goingToEnd ? 1f : 0f);
+ anim.setDuration(animationDuration);
+ anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+ anim.start();
+ }
+
+ private void onCurrentAnimationEnd(boolean wasSuccess) {
+ // TODO: Might be a good time to log something.
+ if (mTaskBeingDragged == null) {
+ LauncherState state = wasSuccess ?
+ (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
+ mLauncher.getStateManager().goToState(state, false);
+ } else if (wasSuccess) {
+ if (mCurrentAnimationIsGoingUp) {
+ mRecentsView.onTaskDismissed(mTaskBeingDragged);
+ } else {
+ mTaskBeingDragged.launchTask(false);
+ }
+ }
+ mDetector.finishedScrolling();
+ mTaskBeingDragged = null;
+ mCurrentAnimation = null;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index bd443aa42..0e539ee0f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -41,11 +41,11 @@ public class UiFactory {
return new TouchController[]{
new EdgeSwipeController(launcher),
new TwoStepSwipeController(launcher),
- new OverviewSwipeUpController(launcher)};
+ new OverviewSwipeController(launcher)};
} else {
return new TouchController[]{
new TwoStepSwipeController(launcher),
- new OverviewSwipeUpController(launcher)};
+ new OverviewSwipeController(launcher)};
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 94d85ee6a..3f733ca1c 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,29 +16,17 @@
package com.android.quickstep;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Property;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
import com.android.quickstep.RecentsView.PageCallbacks;
import com.android.quickstep.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
@@ -52,11 +40,13 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.util.ArrayList;
import java.util.List;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
/**
* A task in the Recents view.
*/
-public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetector.Listener,
- PageCallbacks {
+public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
/** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
private static final float CURVE_FACTOR = 0.25f;
@@ -70,30 +60,8 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
*/
private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
- private static final int SWIPE_DIRECTIONS = SwipeDetector.DIRECTION_POSITIVE;
-
- /**
- * The task will appear fully dismissed when the distance swiped
- * reaches this percentage of the card height.
- */
- private static final float SWIPE_DISTANCE_HEIGHT_PERCENTAGE = 0.38f;
-
private static final long SCALE_ICON_DURATION = 120;
- private static final Property<TaskView, Float> PROPERTY_SWIPE_PROGRESS =
- new Property<TaskView, Float>(Float.class, "swipe_progress") {
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSwipeProgress;
- }
-
- @Override
- public void set(TaskView taskView, Float progress) {
- taskView.setSwipeProgress(progress);
- }
- };
-
private static final Property<TaskView, Float> SCALE_ICON_PROPERTY =
new Property<TaskView, Float>(Float.TYPE, "scale_icon") {
@Override
@@ -110,11 +78,6 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
private Task mTask;
private TaskThumbnailView mSnapshotView;
private ImageView mIconView;
- private SwipeDetector mSwipeDetector;
- private float mSwipeDistance;
- private float mSwipeProgress;
- private Interpolator mAlphaInterpolator;
- private Interpolator mSwipeAnimInterpolator;
private float mIconScale = 1f;
public TaskView(Context context) {
@@ -130,11 +93,6 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
setOnClickListener((view) -> {
launchTask(true /* animate */);
});
-
- mSwipeDetector = new SwipeDetector(getContext(), this, SwipeDetector.VERTICAL);
- mSwipeDetector.setDetectableScrollConditions(SWIPE_DIRECTIONS, false);
- mAlphaInterpolator = Interpolators.ACCEL_1_5;
- mSwipeAnimInterpolator = Interpolators.SCROLL_CUBIC;
}
@Override
@@ -144,15 +102,6 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
mIconView = findViewById(R.id.icon);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- View p = (View) getParent();
- mSwipeDistance = (getMeasuredHeight() - p.getPaddingTop() - p.getPaddingBottom())
- * SWIPE_DISTANCE_HEIGHT_PERCENTAGE;
- }
-
/**
* Updates this task view to the given {@param task}.
*/
@@ -223,80 +172,6 @@ public class TaskView extends FrameLayout implements TaskCallbacks, SwipeDetecto
// Do nothing
}
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- mSwipeDetector.onTouchEvent(ev);
- return super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- mSwipeDetector.onTouchEvent(event);
- return mSwipeDetector.isDraggingOrSettling() || super.onTouchEvent(event);
- }
-
- // Swipe detector methods
-
- @Override
- public void onDragStart(boolean start) {
- getParent().requestDisallowInterceptTouchEvent(true);
- }
-
- @Override
- public boolean onDrag(float displacement, float velocity) {
- setSwipeProgress(Utilities.boundToRange(displacement / mSwipeDistance,
- allowsSwipeUp() ? -1 : 0, allowsSwipeDown() ? 1 : 0));
- return true;
- }
-
- /**
- * Indicates the page is being removed.
- * @param progress Ranges from -1 (fading upwards) to 1 (fading downwards).
- */
- private void setSwipeProgress(float progress) {
- mSwipeProgress = progress;
- float translationY = mSwipeProgress * mSwipeDistance;
- float alpha = 1f - mAlphaInterpolator.getInterpolation(Math.abs(mSwipeProgress));
- // Only change children to avoid changing our properties while dragging.
- mIconView.setTranslationY(translationY);
- mSnapshotView.setTranslationY(translationY);
- mIconView.setAlpha(alpha);
- mSnapshotView.setAlpha(alpha);
- }
-
- private boolean allowsSwipeUp() {
- return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_POSITIVE) != 0;
- }
-
- private boolean allowsSwipeDown() {
- return (SWIPE_DIRECTIONS & SwipeDetector.DIRECTION_NEGATIVE) != 0;
- }
-
- @Override
- public void onDragEnd(float velocity, boolean fling) {
- boolean movingAwayFromCenter = velocity < 0 == mSwipeProgress < 0;
- boolean flingAway = fling && movingAwayFromCenter
- && (allowsSwipeUp() && velocity < 0 || allowsSwipeDown() && velocity > 0);
- final boolean shouldRemove = flingAway || (!fling && Math.abs(mSwipeProgress) > 0.5f);
- float fromProgress = mSwipeProgress;
- float toProgress = !shouldRemove ? 0f : mSwipeProgress < 0 ? -1f : 1f;
- ValueAnimator swipeAnimator = ObjectAnimator.ofFloat(this, PROPERTY_SWIPE_PROGRESS,
- fromProgress, toProgress);
- swipeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (shouldRemove) {
- ((RecentsView) getParent()).onTaskDismissed(TaskView.this);
- }
- mSwipeDetector.finishedScrolling();
- }
- });
- swipeAnimator.setDuration(SwipeDetector.calculateDuration(velocity,
- Math.abs(toProgress - fromProgress)));
- swipeAnimator.setInterpolator(mSwipeAnimInterpolator);
- swipeAnimator.start();
- }
-
public void animateIconToScale(float scale) {
ObjectAnimator.ofFloat(this, SCALE_ICON_PROPERTY, scale)
.setDuration(SCALE_ICON_DURATION).start();