diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2018-01-12 14:17:30 -0800 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2018-01-16 10:31:36 -0800 |
commit | 05a3bbdef8c328370a51e045db12d67e62956b3f (patch) | |
tree | 4f2c04f988d272f2de022f959e5c4fad4970a2d7 /quickstep | |
parent | 9f082604b0b2e9bc5fe857e418739252e7f3e81d (diff) | |
download | android_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')
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(); |