diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index dbb8272621..6a8865a8da 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -71,6 +71,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.View.OnApplyWindowInsetsListener; import android.view.ViewTreeObserver.OnDrawListener; +import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.WindowInsets; import android.view.animation.Interpolator; import android.widget.Toast; @@ -139,6 +140,8 @@ public abstract class AbsSwipeUpHandler, private final Handler mHandler = new Handler(); // Callbacks to be made once the recents animation starts private final ArrayList mRecentsAnimationStartCallbacks = new ArrayList<>(); + private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll; + protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; protected T mActivity; @@ -1368,6 +1371,7 @@ public abstract class AbsSwipeUpHandler, private void invalidateHandlerWithLauncher() { endLauncherTransitionController(); + mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener); mRecentsView.onGestureAnimationEnd(); resetLauncherListeners(); } @@ -1560,17 +1564,19 @@ public abstract class AbsSwipeUpHandler, mRecentsAnimationTargets.addReleaseCheck(applier)); }); - mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { - if (moveWindowWithRecentsScroll()) { - updateFinalShift(); - } - }); + mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener); runOnRecentsAnimationStart(() -> mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController, mRecentsAnimationTargets)); mRecentsViewScrollLinked = true; } + private void onRecentsViewScroll() { + if (moveWindowWithRecentsScroll()) { + updateFinalShift(); + } + } + protected void startNewTask(Consumer resultCallback) { // Launch the task user scrolled to (mRecentsView.getNextPage()). if (!mCanceled) { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 6ed490d6d9..079401205c 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -42,6 +42,7 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; import static com.android.launcher3.statehandlers.DepthController.DEPTH; +import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; @@ -88,11 +89,13 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.ListView; +import android.widget.OverScroller; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -116,14 +119,15 @@ import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.touch.OverScroll; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.MultiValueAlpha; -import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.ResourceBasedOverride.Overrides; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.Themes; +import com.android.launcher3.util.TranslateEdgeEffect; import com.android.launcher3.util.ViewPool; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.BaseActivityInterface; @@ -157,6 +161,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.wm.shell.pip.IPipAnimationListener; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -296,6 +301,9 @@ public abstract class RecentsView mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; @@ -314,6 +322,8 @@ public abstract class RecentsView mScrollListeners = new ArrayList<>(); private float mFullscreenScale; private static final int DISMISS_TASK_DURATION = 300; @@ -360,6 +370,9 @@ public abstract class RecentsView mMinScroll && finalPos < mMaxScroll) { + int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1); + int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0); + + // If scrolling ends in the half of the added space that is closer to + // the end, settle to the end. Otherwise snap to the nearest page. + // If flinging past one of the ends, don't change the velocity as it + // will get stopped at the end anyway. + int pageSnapped = finalPos < (firstPageScroll + mMinScroll) / 2 + ? mMinScroll + : finalPos > (lastPageScroll + mMaxScroll) / 2 + ? mMaxScroll + : getScrollForPage(mNextPage); + + mScroller.setFinalX(pageSnapped); + // Ensure the scroll/snap doesn't happen too fast; + int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION + - mScroller.getDuration(); + if (extraScrollDuration > 0) { + mScroller.extendDuration(extraScrollDuration); + } + } } @Override @@ -1267,12 +1368,6 @@ public abstract class RecentsView 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false; - return super.onOverscroll(amount); - } - /** * @return How many pixels the running task is offset on the currently laid out dominant axis. */ @@ -3228,14 +3315,8 @@ public abstract class RecentsView= 0; i--) { + mScrollListeners.get(i).onScrollChanged(); + } + } + private static class PinnedStackAnimationListener extends IPipAnimationListener.Stub { private T mActivity; diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java index 658d71daf9..f55cdaca46 100644 --- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java @@ -121,7 +121,7 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange }; } - private void setPosition(float x, float y) { + private void setPosition(float x, float y, int overscrollShift) { PagedOrientationHandler pagedOrientationHandler = mTaskView.getPagedOrientationHandler(); int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx; float adjustedY = y + taskTopMargin; @@ -136,8 +136,9 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange setPivotY(0); } setRotation(pagedOrientationHandler.getDegreesRotated()); - setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail())); - setY(pagedOrientationHandler.getTaskMenuY(adjustedY, mTaskView.getThumbnail())); + setX(pagedOrientationHandler.getTaskMenuX(x, mTaskView.getThumbnail(), overscrollShift)); + setY(pagedOrientationHandler.getTaskMenuY( + adjustedY, mTaskView.getThumbnail(), overscrollShift)); } public void onRotationChanged() { @@ -169,14 +170,15 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange return false; } post(this::animateOpen); - mActivity.getRootView().getViewTreeObserver().addOnScrollChangedListener(this); + ((RecentsView) mActivity.getOverviewPanel()).addOnScrollChangedListener(this); return true; } @Override public void onScrollChanged() { RecentsView rv = mTaskView.getRecentsView(); - setPosition(mTaskView.getX() - rv.getScrollX(), mTaskView.getY() - rv.getScrollY()); + setPosition(mTaskView.getX() - rv.getScrollX(), mTaskView.getY() - rv.getScrollY(), + rv.getOverScrollShift()); } /** @return true if successfully able to populate task view menu, false otherwise */ @@ -236,7 +238,7 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange .mOrientationState.isRecentsActivityRotationAllowed(); mOptionLayout.setOrientation(orientationHandler .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout)); - setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top); + setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top, 0); } private void animateOpen() { @@ -282,7 +284,7 @@ public class TaskMenuView extends AbstractFloatingView implements OnScrollChange private void closeComplete() { mIsOpen = false; mActivity.getDragLayer().removeView(this); - mActivity.getRootView().getViewTreeObserver().removeOnScrollChangedListener(this); + ((RecentsView) mActivity.getOverviewPanel()).removeOnScrollChangedListener(this); } private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 7496703f88..c9cc3721a8 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -16,16 +16,14 @@ package com.android.launcher3; +import static com.android.launcher3.anim.Interpolators.SCROLL; import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled; import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType; -import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR; -import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY; import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO; import android.animation.LayoutTransition; -import android.animation.TimeInterpolator; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; @@ -46,19 +44,17 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.Interpolator; +import android.widget.OverScroller; import android.widget.ScrollView; import androidx.annotation.Nullable; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.pageindicators.PageIndicator; -import com.android.launcher3.touch.OverScroll; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds; -import com.android.launcher3.util.OverScroller; +import com.android.launcher3.util.EdgeEffectCompat; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; @@ -80,9 +76,6 @@ public abstract class PagedView extends ViewGrou public static final int PAGE_SNAP_ANIMATION_DURATION = 750; - // OverScroll constants - private final static int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; - private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; @@ -113,7 +106,6 @@ public abstract class PagedView extends ViewGrou protected int mMaxScroll; protected int mMinScroll; protected OverScroller mScroller; - private Interpolator mDefaultInterpolator; private VelocityTracker mVelocityTracker; protected int mPageSpacing = 0; @@ -144,12 +136,6 @@ public abstract class PagedView extends ViewGrou protected boolean mIsPageInTransition = false; private Runnable mOnPageTransitionEndCallback; - protected float mSpringOverScroll; - - protected boolean mWasInOverscroll = false; - - protected int mUnboundedScroll; - // Page Indicator @Thunk int mPageIndicatorViewId; protected T mPageIndicator; @@ -162,6 +148,9 @@ public abstract class PagedView extends ViewGrou private int[] mTmpIntPair = new int[2]; + protected EdgeEffectCompat mEdgeGlowLeft; + protected EdgeEffectCompat mEdgeGlowRight; + public PagedView(Context context) { this(context, null); } @@ -181,8 +170,7 @@ public abstract class PagedView extends ViewGrou setHapticFeedbackEnabled(false); mIsRtl = Utilities.isRtl(getResources()); - mScroller = new OverScroller(context); - setDefaultInterpolator(Interpolators.SCROLL); + mScroller = new OverScroller(context, SCROLL); mCurrentPage = 0; final ViewConfiguration configuration = ViewConfiguration.get(context); @@ -196,12 +184,14 @@ public abstract class PagedView extends ViewGrou mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density); mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); + initEdgeEffect(); setDefaultFocusHighlightEnabled(false); + setWillNotDraw(false); } - protected void setDefaultInterpolator(Interpolator interpolator) { - mDefaultInterpolator = interpolator; - mScroller.setInterpolator(mDefaultInterpolator); + protected void initEdgeEffect() { + mEdgeGlowLeft = new EdgeEffectCompat(getContext()); + mEdgeGlowRight = new EdgeEffectCompat(getContext()); } public void initParentViews(View parent) { @@ -256,7 +246,7 @@ public abstract class PagedView extends ViewGrou newPosition = getScrollForPage(mCurrentPage); } mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition); - mOrientationHandler.scrollerStartScroll(mScroller, newPosition); + mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0); forceFinishScroller(true); } @@ -420,7 +410,6 @@ public abstract class PagedView extends ViewGrou * to provide custom behavior during animation. */ protected void onPageEndTransition() { - mWasInOverscroll = false; AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext()); AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage), AccessibilityEvent.TYPE_VIEW_FOCUSED, null); @@ -442,56 +431,12 @@ public abstract class PagedView extends ViewGrou } } - protected int getUnboundedScroll() { - return mUnboundedScroll; - } - - @Override - public void scrollBy(int x, int y) { - mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y); - } - @Override public void scrollTo(int x, int y) { - int primaryScroll = mOrientationHandler.getPrimaryValue(x, y); - int secondaryScroll = mOrientationHandler.getSecondaryValue(x, y); - mUnboundedScroll = primaryScroll; - - boolean isBeforeFirstPage = mIsRtl ? - (primaryScroll > mMaxScroll) : (primaryScroll < mMinScroll); - boolean isAfterLastPage = mIsRtl ? - (primaryScroll < mMinScroll) : (primaryScroll > mMaxScroll); - if (!isBeforeFirstPage && !isAfterLastPage) { - mSpringOverScroll = 0; - } - - if (isBeforeFirstPage) { - mOrientationHandler.delegateScrollTo(this, - secondaryScroll, mIsRtl ? mMaxScroll : mMinScroll); - if (mAllowOverScroll) { - mWasInOverscroll = true; - overScroll(primaryScroll - (mIsRtl ? mMaxScroll : mMinScroll)); - } - } else if (isAfterLastPage) { - mOrientationHandler.delegateScrollTo(this, - secondaryScroll, mIsRtl ? mMinScroll : mMaxScroll); - if (mAllowOverScroll) { - mWasInOverscroll = true; - overScroll(primaryScroll - (mIsRtl ? mMinScroll : mMaxScroll)); - } - } else { - if (mWasInOverscroll) { - overScroll(0); - mWasInOverscroll = false; - } - super.scrollTo(x, y); - } - } - - /** - * Helper for {@link PagedOrientationHandler} to be able to call parent's scrollTo method - */ - public void superScrollTo(int x, int y) { + x = Utilities.boundToRange(x, + mOrientationHandler.getPrimaryValue(mMinScroll, 0), mMaxScroll); + y = Utilities.boundToRange(y, + mOrientationHandler.getPrimaryValue(0, mMinScroll), mMaxScroll); super.scrollTo(x, y); } @@ -524,12 +469,22 @@ public abstract class PagedView extends ViewGrou protected boolean computeScrollHelper(boolean shouldInvalidate) { if (mScroller.computeScrollOffset()) { // Don't bother scrolling if the page does not need to be moved - int currentScroll = mOrientationHandler.getPrimaryScroll(this); - if (mUnboundedScroll != mScroller.getCurrPos() - || currentScroll != mScroller.getCurrPos()) { - mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrPos()); + int oldPos = mOrientationHandler.getPrimaryScroll(this); + int newPos = mScroller.getCurrX(); + if (oldPos != newPos) { + mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrX()); } if (shouldInvalidate) { + if (mAllowOverScroll) { + if (newPos < mMinScroll && oldPos >= mMinScroll) { + mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); + mScroller.abortAnimation(); + } else if (newPos > mMaxScroll && oldPos <= mMaxScroll) { + mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); + mScroller.abortAnimation(); + } + } + invalidate(); } return true; @@ -982,9 +937,7 @@ public abstract class PagedView extends ViewGrou mTotalMotion = 0; mAllowEasyFling = false; mActivePointerId = ev.getPointerId(0); - - updateIsBeingDraggedOnTouchDown(); - + updateIsBeingDraggedOnTouchDown(ev); break; } @@ -1009,9 +962,9 @@ public abstract class PagedView extends ViewGrou /** * If being flinged and user touches the screen, initiate drag; otherwise don't. */ - private void updateIsBeingDraggedOnTouchDown() { + private void updateIsBeingDraggedOnTouchDown(MotionEvent ev) { // mScroller.isFinished should be false when being flinged. - final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos()); + final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); final boolean finishedScrolling = (mScroller.isFinished() || xDist < mPageSlop / 3); if (finishedScrolling) { @@ -1020,9 +973,20 @@ public abstract class PagedView extends ViewGrou setCurrentPage(getNextPage()); pageEndTransition(); } + mIsBeingDragged = !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished(); } else { mIsBeingDragged = true; } + + // Catch the edge effect if it is active. + float displacement = mOrientationHandler.getSecondaryValue(ev.getX(), ev.getY()) + / mOrientationHandler.getSecondaryValue(getWidth(), getHeight()); + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onPullDistance(0f, 1f - displacement); + } + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onPullDistance(0f, displacement); + } } public boolean isHandlingTouch() { @@ -1053,7 +1017,6 @@ public abstract class PagedView extends ViewGrou mTotalMotion += Math.abs(mLastMotion - primaryDirection); mLastMotion = primaryDirection; mLastMotionRemainder = 0; - onScrollInteractionBegin(); pageBeginTransition(); // Stop listening for things like pinches. requestDisallowInterceptTouchEvent(true); @@ -1114,69 +1077,6 @@ public abstract class PagedView extends ViewGrou } } - @Override - protected void dispatchDraw(Canvas canvas) { - if (mScroller.isSpringing() && mSpringOverScroll != 0) { - int saveCount = canvas.save(); - mOrientationHandler.set(canvas, CANVAS_TRANSLATE, -mSpringOverScroll); - super.dispatchDraw(canvas); - - canvas.restoreToCount(saveCount); - } else { - super.dispatchDraw(canvas); - } - } - - /** - * Returns the amount of overscroll caused by the spring in {@link OverScroller}. - */ - private int getSpringOverScroll(int amount) { - if (mScroller.isSpringing()) { - return amount < 0 - ? mScroller.getCurrPos() - mMinScroll - : Math.max(0, mScroller.getCurrPos() - mMaxScroll); - } else { - return 0; - } - } - - protected void dampedOverScroll(int amount) { - if (amount == 0) { - return; - } - - int size = mOrientationHandler.getMeasuredSize(this); - int overScrollAmount = OverScroll.dampedScroll(amount, size); - if (mScroller.isSpringing()) { - mSpringOverScroll = getSpringOverScroll(amount); - invalidate(); - return; - } - - int primaryScroll = mOrientationHandler.getPrimaryScroll(this); - int boundedScroll = Utilities.boundToRange(primaryScroll, mMinScroll, mMaxScroll); - mOrientationHandler.delegateScrollTo(this, boundedScroll + overScrollAmount); - invalidate(); - } - - protected void overScroll(int amount) { - if (mScroller.isSpringing()) { - mSpringOverScroll = getSpringOverScroll(amount); - invalidate(); - return; - } - - if (amount == 0) return; - - if (mFreeScroll && !mScroller.isFinished()) { - int scrollAmount = amount < 0 ? mMinScroll + amount : mMaxScroll + amount; - mOrientationHandler.delegateScrollTo(this, scrollAmount); - } else { - dampedOverScroll(amount); - } - } - - public void setEnableFreeScroll(boolean freeScroll) { if (mFreeScroll == freeScroll) { return; @@ -1209,7 +1109,7 @@ public abstract class PagedView extends ViewGrou switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: - updateIsBeingDraggedOnTouchDown(); + updateIsBeingDraggedOnTouchDown(ev); /* * If being flinged and user touches, stop the fling. isFinished @@ -1228,7 +1128,6 @@ public abstract class PagedView extends ViewGrou mAllowEasyFling = false; mActivePointerId = ev.getPointerId(0); if (mIsBeingDragged) { - onScrollInteractionBegin(); pageBeginTransition(); } break; @@ -1245,19 +1144,62 @@ public abstract class PagedView extends ViewGrou final int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) return true; + float oldScroll = mOrientationHandler.getPrimaryScroll(this); + float dx = ev.getX(pointerIndex); + float dy = ev.getY(pointerIndex); - float direction = mOrientationHandler.getPrimaryDirection(ev, pointerIndex); + float direction = mOrientationHandler.getPrimaryValue(dx, dy); float delta = mLastMotion + mLastMotionRemainder - direction; + + int width = getWidth(); + int height = getHeight(); + int size = mOrientationHandler.getPrimaryValue(width, height); + + final float displacement = mOrientationHandler.getSecondaryValue(dx, dy) + / mOrientationHandler.getSecondaryValue(width, height); mTotalMotion += Math.abs(delta); + if (mAllowOverScroll) { + float consumed = 0; + if (delta < 0 && mEdgeGlowRight.getDistance() != 0f) { + consumed = size * mEdgeGlowRight.onPullDistance(delta / size, displacement); + } else if (delta > 0 && mEdgeGlowLeft.getDistance() != 0f) { + consumed = -size * mEdgeGlowLeft.onPullDistance( + -delta / size, 1 - displacement); + } + delta -= consumed; + } + // Only scroll and update mLastMotionX if we have moved some discrete amount. We // keep the remainder because we are actually testing if we've moved from the last // scrolled position (which is discrete). - if (Math.abs(delta) >= 1.0f) { - mLastMotion = direction; - mLastMotionRemainder = delta - (int) delta; + mLastMotion = direction; + int movedDelta = (int) delta; + mLastMotionRemainder = delta - movedDelta; + + if (delta != 0) { + mOrientationHandler.set(this, VIEW_SCROLL_BY, movedDelta); + + if (mAllowOverScroll) { + final float pulledToX = oldScroll + delta; + + if (pulledToX < mMinScroll) { + mEdgeGlowLeft.onPullDistance(-delta / size, 1.f - displacement); + if (!mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onRelease(); + } + } else if (pulledToX > mMaxScroll) { + mEdgeGlowRight.onPullDistance(delta / size, displacement); + if (!mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onRelease(); + } + } + + if (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished()) { + postInvalidateOnAnimation(); + } + } - mOrientationHandler.set(this, VIEW_SCROLL_BY, (int) delta); } else { awakenScrollBars(); } @@ -1335,45 +1277,24 @@ public abstract class PagedView extends ViewGrou if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) || ((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) { - mScroller.springBack(initialScroll, minScroll, maxScroll); + mScroller.springBack(initialScroll, 0, minScroll, maxScroll, 0, 0); mNextPage = getDestinationPage(); } else { - mScroller.setInterpolator(mDefaultInterpolator); - mScroller.fling(initialScroll, -velocity, - minScroll, maxScroll, - Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR)); + int velocity1 = -velocity; + // Continue a scroll or fling in progress + mScroller.fling(initialScroll, 0, velocity1, 0, minScroll, maxScroll, 0, 0, + Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR), 0); - int finalPos = mScroller.getFinalPos(); + int finalPos = mScroller.getFinalX(); mNextPage = getDestinationPage(finalPos); - - int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1); - int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0); - if (snapToPageInFreeScroll() && finalPos > minScroll - && finalPos < maxScroll) { - // If scrolling ends in the half of the added space that is closer to - // the end, settle to the end. Otherwise snap to the nearest page. - // If flinging past one of the ends, don't change the velocity as it - // will get stopped at the end anyway. - int pageSnapped = finalPos < (firstPageScroll + minScroll) / 2 - ? minScroll - : finalPos > (lastPageScroll + maxScroll) / 2 - ? maxScroll - : getScrollForPage(mNextPage); - - mScroller.setFinalPos(pageSnapped); - // Ensure the scroll/snap doesn't happen too fast; - int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION - - mScroller.getDuration(); - if (extraScrollDuration > 0) { - mScroller.extendDuration(extraScrollDuration); - } - } + onNotSnappingToPageInFreeScroll(); } invalidate(); } - onScrollInteractionEnd(); } + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); // End any intermediate reordering states resetTouchState(); break; @@ -1381,8 +1302,9 @@ public abstract class PagedView extends ViewGrou case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged) { snapToDestination(); - onScrollInteractionEnd(); } + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); resetTouchState(); break; @@ -1395,9 +1317,7 @@ public abstract class PagedView extends ViewGrou return true; } - protected boolean snapToPageInFreeScroll() { - return true; - } + protected void onNotSnappingToPageInFreeScroll() { } protected boolean shouldFlingForVelocity(int velocity) { float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity; @@ -1410,15 +1330,6 @@ public abstract class PagedView extends ViewGrou mActivePointerId = INVALID_POINTER; } - /** - * Triggered by scrolling via touch - */ - protected void onScrollInteractionBegin() { - } - - protected void onScrollInteractionEnd() { - } - @Override public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { @@ -1554,19 +1465,7 @@ public abstract class PagedView extends ViewGrou } protected void snapToDestination() { - snapToPage(getDestinationPage(), getPageSnapDuration()); - } - - protected boolean isInOverScroll() { - int scroll = mOrientationHandler.getPrimaryScroll(this); - return scroll > mMaxScroll || scroll < mMinScroll; - } - - protected int getPageSnapDuration() { - if (isInOverScroll()) { - return OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION; - } - return PAGE_SNAP_ANIMATION_DURATION; + snapToPage(getDestinationPage(), PAGE_SNAP_ANIMATION_DURATION); } // We want the duration of the page snap animation to be influenced by the distance that @@ -1584,7 +1483,7 @@ public abstract class PagedView extends ViewGrou int halfScreenSize = mOrientationHandler.getMeasuredSize(this) / 2; final int newLoc = getScrollForPage(whichPage); - int delta = newLoc - getUnboundedScroll(); + int delta = newLoc - mOrientationHandler.getPrimaryScroll(this); int duration = 0; if (Math.abs(velocity) < mMinFlingVelocity) { @@ -1609,12 +1508,7 @@ public abstract class PagedView extends ViewGrou // interpolator at zero, ie. 5. We use 4 to make it a little slower. duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - if (QUICKSTEP_SPRINGS.get() && mCurrentPage != whichPage) { - return snapToPage(whichPage, delta, duration, false, null, - velocity * Math.signum(delta), true); - } else { - return snapToPage(whichPage, delta, duration); - } + return snapToPage(whichPage, delta, duration); } public boolean snapToPage(int whichPage) { @@ -1622,32 +1516,26 @@ public abstract class PagedView extends ViewGrou } public boolean snapToPageImmediately(int whichPage) { - return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null); + return snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true); } public boolean snapToPage(int whichPage, int duration) { - return snapToPage(whichPage, duration, false, null); + return snapToPage(whichPage, duration, false); } - public boolean snapToPage(int whichPage, int duration, TimeInterpolator interpolator) { - return snapToPage(whichPage, duration, false, interpolator); - } - - protected boolean snapToPage(int whichPage, int duration, boolean immediate, - TimeInterpolator interpolator) { + protected boolean snapToPage(int whichPage, int duration, boolean immediate) { whichPage = validateNewPage(whichPage); int newLoc = getScrollForPage(whichPage); - final int delta = newLoc - getUnboundedScroll(); - return snapToPage(whichPage, delta, duration, immediate, interpolator, 0, false); + final int delta = newLoc - mOrientationHandler.getPrimaryScroll(this); + return snapToPage(whichPage, delta, duration, immediate); } protected boolean snapToPage(int whichPage, int delta, int duration) { - return snapToPage(whichPage, delta, duration, false, null, 0, false); + return snapToPage(whichPage, delta, duration, false); } - protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate, - TimeInterpolator interpolator, float velocity, boolean spring) { + protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate) { if (mFirstLayout) { setCurrentPage(whichPage); return false; @@ -1677,18 +1565,7 @@ public abstract class PagedView extends ViewGrou abortScrollerAnimation(false); } - if (interpolator != null) { - mScroller.setInterpolator(interpolator); - } else { - mScroller.setInterpolator(mDefaultInterpolator); - } - - if (spring && QUICKSTEP_SPRINGS.get()) { - mScroller.startScrollSpring(getUnboundedScroll(), delta, duration, velocity); - } else { - mScroller.startScroll(getUnboundedScroll(), delta, duration); - } - + mScroller.startScroll(mOrientationHandler.getPrimaryScroll(this), 0, delta, 0, duration); updatePageIndicator(); // Trigger a compute() to finish switching pages if necessary @@ -1706,7 +1583,7 @@ public abstract class PagedView extends ViewGrou snapToPage(getNextPage() - 1); return true; } - return onOverscroll(-getMeasuredWidth()); + return mAllowOverScroll; } public boolean scrollRight() { @@ -1714,15 +1591,7 @@ public abstract class PagedView extends ViewGrou snapToPage(getNextPage() + 1); return true; } - return onOverscroll(getMeasuredWidth()); - } - - protected boolean onOverscroll(int amount) { - if (!mAllowOverScroll) return false; - onScrollInteractionBegin(); - overScroll(amount); - onScrollInteractionEnd(); - return true; + return mAllowOverScroll; } @Override @@ -1866,4 +1735,38 @@ public abstract class PagedView extends ViewGrou mTmpIntPair[1] = rightChild; return mTmpIntPair; } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + drawEdgeEffect(canvas); + } + + protected void drawEdgeEffect(Canvas canvas) { + if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) { + final int width = getWidth(); + final int height = getHeight(); + if (!mEdgeGlowLeft.isFinished()) { + final int restoreCount = canvas.save(); + canvas.rotate(-90); + canvas.translate(-height, Math.min(mMinScroll, getScrollX())); + mEdgeGlowLeft.setSize(height, width); + if (mEdgeGlowLeft.draw(canvas)) { + postInvalidateOnAnimation(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowRight.isFinished()) { + final int restoreCount = canvas.save(); + canvas.rotate(90, width, 0); + canvas.translate(width, -(Math.max(mMaxScroll, getScrollX()))); + + mEdgeGlowRight.setSize(height, width); + if (mEdgeGlowRight.draw(canvas)) { + postInvalidateOnAnimation(); + } + canvas.restoreToCount(restoreCount); + } + } + } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 10091a18b8..147b3e4639 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -24,7 +24,6 @@ import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DR import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; -import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY; @@ -96,10 +95,12 @@ import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.WorkspaceTouchListener; +import com.android.launcher3.util.EdgeEffectCompat; import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.OverlayEdgeEffect; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperOffsetInterpolator; @@ -245,10 +246,7 @@ public class Workspace extends PagedView private float mTransitionProgress; // State related to Launcher Overlay - LauncherOverlay mLauncherOverlay; - boolean mScrollInteractionBegan; - boolean mStartedSendingScrollEvents; - float mLastOverlayScroll = 0; + private OverlayEdgeEffect mOverlayEdgeEffect; boolean mOverlayShown = false; private Runnable mOnOverlayHiddenCallback; @@ -945,47 +943,25 @@ public class Workspace extends PagedView } } - protected void onScrollInteractionBegin() { - super.onScrollInteractionBegin(); - mScrollInteractionBegan = true; - } - - protected void onScrollInteractionEnd() { - super.onScrollInteractionEnd(); - mScrollInteractionBegan = false; - if (mStartedSendingScrollEvents) { - mStartedSendingScrollEvents = false; - mLauncherOverlay.onScrollInteractionEnd(); - } - } - public void setLauncherOverlay(LauncherOverlay overlay) { - mLauncherOverlay = overlay; - // A new overlay has been set. Reset event tracking - mStartedSendingScrollEvents = false; + mOverlayEdgeEffect = overlay == null ? null : new OverlayEdgeEffect(getContext(), overlay); + EdgeEffectCompat newEffect = overlay == null + ? new EdgeEffectCompat(getContext()) : mOverlayEdgeEffect; + if (mIsRtl) { + mEdgeGlowRight = newEffect; + } else { + mEdgeGlowLeft = newEffect; + } onOverlayScrollChanged(0); } public boolean hasOverlay() { - return mLauncherOverlay != null; - } - - private boolean isScrollingOverlay() { - return mLauncherOverlay != null && - ((mIsRtl && getUnboundedScroll() > mMaxScroll) - || (!mIsRtl && getUnboundedScroll() < mMinScroll)); + return mOverlayEdgeEffect != null; } @Override protected void snapToDestination() { - // If we're overscrolling the overlay, we make sure to immediately reset the PagedView - // to it's baseline position instead of letting the overscroll settle. The overlay handles - // it's own settling, and every gesture to the overlay should be self-contained and start - // from 0, so we zero it out here. - if (isScrollingOverlay()) { - // We reset mWasInOverscroll so that PagedView doesn't zero out the overscroll - // interaction when we call snapToPageImmediately. - mWasInOverscroll = false; + if (mOverlayEdgeEffect != null && !mOverlayEdgeEffect.isFinished()) { snapToPageImmediately(0); } else { super.snapToDestination(); @@ -1017,38 +993,6 @@ public class Workspace extends PagedView } } - @Override - protected void overScroll(int amount) { - boolean shouldScrollOverlay = mLauncherOverlay != null && !mScroller.isSpringing() && - ((amount <= 0 && !mIsRtl) || (amount >= 0 && mIsRtl)); - - boolean shouldZeroOverlay = mLauncherOverlay != null && mLastOverlayScroll != 0 && - ((amount >= 0 && !mIsRtl) || (amount <= 0 && mIsRtl)); - - if (shouldScrollOverlay) { - if (!mStartedSendingScrollEvents && mScrollInteractionBegan) { - mStartedSendingScrollEvents = true; - mLauncherOverlay.onScrollInteractionBegin(); - } - - mLastOverlayScroll = Math.abs(((float) amount) / getMeasuredWidth()); - mLauncherOverlay.onScrollChange(mLastOverlayScroll, mIsRtl); - } else { - dampedOverScroll(amount); - } - - if (shouldZeroOverlay) { - mLauncherOverlay.onScrollChange(0, mIsRtl); - } - } - - @Override - protected boolean onOverscroll(int amount) { - // Enforce overscroll on -1 direction - if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false; - return super.onOverscroll(amount); - } - @Override protected boolean shouldFlingForVelocity(int velocityX) { // When the overlay is moving, the fling or settle transition is controlled by the overlay. @@ -1377,10 +1321,6 @@ public class Workspace extends PagedView mOutlineProvider = outlineProvider; } - public void snapToPageFromOverView(int whichPage) { - snapToPage(whichPage, OVERVIEW.getTransitionDuration(mLauncher), Interpolators.ZOOM_IN); - } - private void onStartStateTransition(LauncherState state) { mIsSwitchingState = true; mTransitionProgress = 0; diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index c1f4643f63..a4e8be6974 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -42,7 +42,6 @@ import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -433,8 +432,7 @@ public class FolderPagedView extends PagedView { int scroll = getScrollForPage(getNextPage()) + hint; int delta = scroll - getScrollX(); if (delta != 0) { - mScroller.setInterpolator(Interpolators.DEACCEL); - mScroller.startScroll(getScrollX(), delta, Folder.SCROLL_HINT_DURATION); + mScroller.startScroll(getScrollX(), 0, delta, 0, Folder.SCROLL_HINT_DURATION); invalidate(); } } diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index 18e27a4ba5..2254ab3cea 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -38,10 +38,8 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import java.util.Collections; @@ -60,18 +58,23 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { } @Override - public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int minMaxScroll) { - pagedView.superScrollTo(secondaryScroll, minMaxScroll); + public int getPrimaryValue(int x, int y) { + return y; } @Override - public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) { - pagedView.scrollTo(pagedView.getScrollX() + x, unboundedScroll + y); + public int getSecondaryValue(int x, int y) { + return x; } @Override - public void scrollerStartScroll(OverScroller scroller, int newPosition) { - scroller.startScroll(scroller.getCurrPos(), newPosition - scroller.getCurrPos()); + public float getPrimaryValue(float x, float y) { + return y; + } + + @Override + public float getSecondaryValue(float x, float y) { + return x; } @Override @@ -86,11 +89,6 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { velocity.set(-oldY, oldX); } - @Override - public void delegateScrollTo(PagedView pagedView, int primaryScroll) { - pagedView.superScrollTo(pagedView.getScrollX(), primaryScroll); - } - @Override public void set(T target, Int2DAction action, int param) { action.call(target, 0, param); @@ -241,13 +239,13 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { } @Override - public float getTaskMenuX(float x, View thumbnailView) { + public float getTaskMenuX(float x, View thumbnailView, int overScroll) { return thumbnailView.getMeasuredWidth() + x; } @Override - public float getTaskMenuY(float y, View thumbnailView) { - return y; + public float getTaskMenuY(float y, View thumbnailView, int overScroll) { + return y + overScroll; } @Override diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index 560df86379..c9149ff6b7 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -30,8 +30,6 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.PagedView; -import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; @@ -89,16 +87,19 @@ public interface PagedOrientationHandler { boolean getRecentsRtlSetting(Resources resources); float getDegreesRotated(); int getRotation(); + T getPrimaryValue(T x, T y); T getSecondaryValue(T x, T y); - void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll); - /** Uses {@params pagedView}.getScroll[X|Y]() method for the secondary amount*/ - void delegateScrollTo(PagedView pagedView, int primaryScroll); - void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y); - void scrollerStartScroll(OverScroller scroller, int newPosition); + + int getPrimaryValue(int x, int y); + int getSecondaryValue(int x, int y); + + float getPrimaryValue(float x, float y); + float getSecondaryValue(float x, float y); + boolean isLayoutNaturalToLauncher(); - float getTaskMenuX(float x, View thumbnailView); - float getTaskMenuY(float y, View thumbnailView); + float getTaskMenuX(float x, View thumbnailView, int overScroll); + float getTaskMenuY(float y, View thumbnailView, int overScroll); int getTaskMenuWidth(View view); int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout); void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp); diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index 86508c4ee2..b2cc31bcd0 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -36,10 +36,8 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import java.util.ArrayList; @@ -58,18 +56,23 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { } @Override - public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll) { - pagedView.superScrollTo(primaryScroll, secondaryScroll); + public int getPrimaryValue(int x, int y) { + return x; } @Override - public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) { - pagedView.scrollTo(unboundedScroll + x, pagedView.getScrollY() + y); + public int getSecondaryValue(int x, int y) { + return y; } @Override - public void scrollerStartScroll(OverScroller scroller, int newPosition) { - scroller.startScroll(newPosition - scroller.getCurrPos(), scroller.getCurrPos()); + public float getPrimaryValue(float x, float y) { + return x; + } + + @Override + public float getSecondaryValue(float x, float y) { + return y; } @Override @@ -82,11 +85,6 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { //no-op } - @Override - public void delegateScrollTo(PagedView pagedView, int primaryScroll) { - pagedView.superScrollTo(primaryScroll, pagedView.getScrollY()); - } - @Override public void set(T target, Int2DAction action, int param) { action.call(target, param, 0); @@ -240,12 +238,12 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { } @Override - public float getTaskMenuX(float x, View thumbnailView) { - return x; + public float getTaskMenuX(float x, View thumbnailView, int overScroll) { + return x + overScroll; } @Override - public float getTaskMenuY(float y, View thumbnailView) { + public float getTaskMenuY(float y, View thumbnailView, int overScroll) { return y; } diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java index bd6e31be02..893a274e9e 100644 --- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -79,13 +79,13 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler { } @Override - public float getTaskMenuX(float x, View thumbnailView) { + public float getTaskMenuX(float x, View thumbnailView, int overScroll) { return x; } @Override - public float getTaskMenuY(float y, View thumbnailView) { - return y + thumbnailView.getMeasuredHeight(); + public float getTaskMenuY(float y, View thumbnailView, int overScroll) { + return y + thumbnailView.getMeasuredHeight() + overScroll; } @Override diff --git a/src/com/android/launcher3/util/EdgeEffectCompat.java b/src/com/android/launcher3/util/EdgeEffectCompat.java new file mode 100644 index 0000000000..491582b87f --- /dev/null +++ b/src/com/android/launcher3/util/EdgeEffectCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 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.util; + +import android.content.Context; +import android.widget.EdgeEffect; + +import com.android.launcher3.Utilities; + +/** + * Extension of {@link EdgeEffect} to allow backwards compatibility + */ +public class EdgeEffectCompat extends EdgeEffect { + + public EdgeEffectCompat(Context context) { + super(context); + } + + @Override + public float getDistance() { + return Utilities.ATLEAST_S ? super.getDistance() : 0; + } + + @Override + public float onPullDistance(float deltaDistance, float displacement) { + if (Utilities.ATLEAST_S) { + return super.onPullDistance(deltaDistance, displacement); + } else { + onPull(deltaDistance, displacement); + return deltaDistance; + } + } +} diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java deleted file mode 100644 index 87e698626d..0000000000 --- a/src/com/android/launcher3/util/OverScroller.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * Copyright (C) 2010 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.util; - -import static com.android.launcher3.anim.Interpolators.SCROLL; - -import android.animation.TimeInterpolator; -import android.content.Context; -import android.hardware.SensorManager; -import android.util.Log; -import android.view.ViewConfiguration; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - -import com.android.launcher3.R; -import com.android.systemui.plugins.ResourceProvider; - -/** - * Based on {@link android.widget.OverScroller} supporting only 1-d scrolling and with more - * customization options. - */ -public class OverScroller { - private int mMode; - - private final SplineOverScroller mScroller; - - private TimeInterpolator mInterpolator; - - private final boolean mFlywheel; - - private static final int DEFAULT_DURATION = 250; - private static final int SCROLL_MODE = 0; - private static final int FLING_MODE = 1; - - /** - * Creates an OverScroller with a viscous fluid scroll interpolator and flywheel. - * @param context - */ - public OverScroller(Context context) { - this(context, null); - } - - /** - * Creates an OverScroller with flywheel enabled. - * @param context The context of this application. - * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will - * be used. - */ - public OverScroller(Context context, Interpolator interpolator) { - this(context, interpolator, true); - } - - /** - * Creates an OverScroller. - * @param context The context of this application. - * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will - * be used. - * @param flywheel If true, successive fling motions will keep on increasing scroll speed. - */ - public OverScroller(Context context, Interpolator interpolator, boolean flywheel) { - if (interpolator == null) { - mInterpolator = SCROLL; - } else { - mInterpolator = interpolator; - } - mFlywheel = flywheel; - mScroller = new SplineOverScroller(context); - } - - public void setInterpolator(TimeInterpolator interpolator) { - if (interpolator == null) { - mInterpolator = SCROLL; - } else { - mInterpolator = interpolator; - } - } - - /** - * The amount of friction applied to flings. The default value - * is {@link ViewConfiguration#getScrollFriction}. - * - * @param friction A scalar dimension-less value representing the coefficient of - * friction. - */ - public final void setFriction(float friction) { - mScroller.setFriction(friction); - } - - /** - * - * Returns whether the scroller has finished scrolling. - * - * @return True if the scroller has finished scrolling, false otherwise. - */ - public final boolean isFinished() { - return mScroller.mFinished; - } - - /** - * Force the finished field to a particular value. Contrary to - * {@link #abortAnimation()}, forcing the animation to finished - * does NOT cause the scroller to move to the final x and y - * position. - * - * @param finished The new finished value. - */ - public final void forceFinished(boolean finished) { - mScroller.mFinished = finished; - } - - /** - * Returns the current offset in the scroll. - * - * @return The new offset as an absolute distance from the origin. - */ - public final int getCurrPos() { - return mScroller.mCurrentPosition; - } - - /** - * Returns the absolute value of the current velocity. - * - * @return The original velocity less the deceleration, norm of the X and Y velocity vector. - */ - public float getCurrVelocity() { - return mScroller.mCurrVelocity; - } - - /** - * Returns the start offset in the scroll. - * - * @return The start offset as an absolute distance from the origin. - */ - public final int getStartPos() { - return mScroller.mStart; - } - - /** - * Returns where the scroll will end. Valid only for "fling" scrolls. - * - * @return The final offset as an absolute distance from the origin. - */ - public final int getFinalPos() { - return mScroller.mFinal; - } - - /** - * Returns how long the scroll event will take, in milliseconds. - * - * Note that if mScroller.mState == SPRING, this duration is ignored, so can only - * serve as an estimate for how long the spring-controlled scroll will take. - * - * @return The duration of the scroll in milliseconds. - */ - public final int getDuration() { - return mScroller.mDuration; - } - - /** - * Extend the scroll animation. This allows a running animation to scroll - * further and longer, when used with {@link #setFinalPos(int)}. - * - * @param extend Additional time to scroll in milliseconds. - * @see #setFinalPos(int) - */ - public void extendDuration(int extend) { - mScroller.extendDuration(extend); - } - - /** - * Sets the final position for this scroller. - * - * @param newPos The new offset as an absolute distance from the origin. - * @see #extendDuration(int) - */ - public void setFinalPos(int newPos) { - mScroller.setFinalPosition(newPos); - } - - /** - * Call this when you want to know the new location. If it returns true, the - * animation is not yet finished. - */ - public boolean computeScrollOffset() { - if (isFinished()) { - return false; - } - - switch (mMode) { - case SCROLL_MODE: - if (isSpringing()) { - return true; - } - long time = AnimationUtils.currentAnimationTimeMillis(); - // Any scroller can be used for time, since they were started - // together in scroll mode. We use X here. - final long elapsedTime = time - mScroller.mStartTime; - - final int duration = mScroller.mDuration; - if (elapsedTime < duration) { - final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration); - mScroller.updateScroll(q); - } else { - abortAnimation(); - } - break; - - case FLING_MODE: - if (!mScroller.mFinished) { - if (!mScroller.update()) { - if (!mScroller.continueWhenFinished()) { - mScroller.finish(); - } - } - } - - break; - } - - return true; - } - - /** - * Start scrolling by providing a starting point and the distance to travel. - * The scroll will use the default value of 250 milliseconds for the - * duration. - * - * @param start Starting horizontal scroll offset in pixels. Positive - * numbers will scroll the content to the left. - * @param delta Distance to travel. Positive numbers will scroll the - * content to the left. - */ - public void startScroll(int start, int delta) { - startScroll(start, delta, DEFAULT_DURATION); - } - - /** - * Start scrolling by providing a starting point and the distance to travel. - * - * @param start Starting scroll offset in pixels. Positive - * numbers will scroll the content to the left. - * @param delta Distance to travel. Positive numbers will scroll the - * content to the left. - * @param duration Duration of the scroll in milliseconds. - */ - public void startScroll(int start, int delta, int duration) { - mMode = SCROLL_MODE; - mScroller.startScroll(start, delta, duration); - } - - /** - * Start scrolling using a spring by providing a starting point and the distance to travel. - * - * @param start Starting scroll offset in pixels. Positive - * numbers will scroll the content to the left. - * @param delta Distance to travel. Positive numbers will scroll the - * content to the left. - * @param duration Duration of the scroll in milliseconds. - * @param velocity The starting velocity for the spring in px per ms. - */ - public void startScrollSpring(int start, int delta, int duration, float velocity) { - mMode = SCROLL_MODE; - mScroller.mState = mScroller.SPRING; - mScroller.startScroll(start, delta, duration, velocity); - } - - /** - * Call this when you want to 'spring back' into a valid coordinate range. - * - * @param start Starting X coordinate - * @param min Minimum valid X value - * @param max Maximum valid X value - * @return true if a springback was initiated, false if startX and startY were - * already within the valid range. - */ - public boolean springBack(int start, int min, int max) { - mMode = FLING_MODE; - return mScroller.springback(start, min, max); - } - - public void fling(int start, int velocity, int min, int max) { - fling(start, velocity, min, max, 0); - } - - /** - * Start scrolling based on a fling gesture. The distance traveled will - * depend on the initial velocity of the fling. - * @param start Starting point of the scroll (X) - * @param velocity Initial velocity of the fling (X) measured in pixels per - * second. - * @param min Minimum X value. The scroller will not scroll past this point - * unless overX > 0. If overfling is allowed, it will use minX as - * a springback boundary. - * @param max Maximum X value. The scroller will not scroll past this point -* unless overX > 0. If overfling is allowed, it will use maxX as -* a springback boundary. - * @param over Overfling range. If > 0, horizontal overfling in either -* direction will be possible. - */ - public void fling(int start, int velocity, int min, int max, int over) { - // Continue a scroll or fling in progress - if (mFlywheel && !isFinished()) { - float oldVelocityX = mScroller.mCurrVelocity; - if (Math.signum(velocity) == Math.signum(oldVelocityX)) { - velocity += oldVelocityX; - } - } - - mMode = FLING_MODE; - mScroller.fling(start, velocity, min, max, over); - } - - /** - * Notify the scroller that we've reached a horizontal boundary. - * Normally the information to handle this will already be known - * when the animation is started, such as in a call to one of the - * fling functions. However there are cases where this cannot be known - * in advance. This function will transition the current motion and - * animate from startX to finalX as appropriate. - * @param start Starting/current X position - * @param finalPos Desired final X position - * @param over Magnitude of overscroll allowed. This should be the maximum - */ - public void notifyEdgeReached(int start, int finalPos, int over) { - mScroller.notifyEdgeReached(start, finalPos, over); - } - - /** - * Returns whether the current Scroller is currently returning to a valid position. - * Valid bounds were provided by the - * {@link #fling(int, int, int, int, int)} method. - * - * One should check this value before calling - * {@link #startScroll(int, int)} as the interpolation currently in progress - * to restore a valid position will then be stopped. The caller has to take into account - * the fact that the started scroll will start from an overscrolled position. - * - * @return true when the current position is overscrolled and in the process of - * interpolating back to a valid value. - */ - public boolean isOverScrolled() { - return (!mScroller.mFinished && mScroller.mState != SplineOverScroller.SPLINE); - } - - /** - * Stops the animation. Contrary to {@link #forceFinished(boolean)}, - * aborting the animating causes the scroller to move to the final x and y - * positions. - * - * @see #forceFinished(boolean) - */ - public void abortAnimation() { - mScroller.finish(); - } - - /** - * Returns the time elapsed since the beginning of the scrolling. - * - * @return The elapsed time in milliseconds. - * - * @hide - */ - public int timePassed() { - final long time = AnimationUtils.currentAnimationTimeMillis(); - return (int) (time - mScroller.mStartTime); - } - - public boolean isSpringing() { - return mScroller.mState == SplineOverScroller.SPRING && !isFinished(); - } - - static class SplineOverScroller { - // Initial position - private int mStart; - - // Current position - private int mCurrentPosition; - - // Final position - private int mFinal; - - // Initial velocity - private int mVelocity; - - // Current velocity - private float mCurrVelocity; - - // Constant current deceleration - private float mDeceleration; - - // Animation starting time, in system milliseconds - private long mStartTime; - - // Animation duration, in milliseconds - private int mDuration; - - // Duration to complete spline component of animation - private int mSplineDuration; - - // Distance to travel along spline animation - private int mSplineDistance; - - // Whether the animation is currently in progress - private boolean mFinished; - - // The allowed overshot distance before boundary is reached. - private int mOver; - - // Fling friction - private float mFlingFriction = ViewConfiguration.getScrollFriction(); - - // Current state of the animation. - private int mState = SPLINE; - - private Context mContext; - private SpringAnimation mSpring; - - // Constant gravity value, used in the deceleration phase. - private static final float GRAVITY = 2000.0f; - - // A context-specific coefficient adjusted to physical values. - private float mPhysicalCoeff; - - private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9)); - private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1) - private static final float START_TENSION = 0.5f; - private static final float END_TENSION = 1.0f; - private static final float P1 = START_TENSION * INFLEXION; - private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION); - - private static final int NB_SAMPLES = 100; - private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1]; - private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1]; - - private static final int SPLINE = 0; - private static final int CUBIC = 1; - private static final int BALLISTIC = 2; - private static final int SPRING = 3; - - private static final FloatPropertyCompat SPRING_PROPERTY = - new FloatPropertyCompat("splineOverScrollerSpring") { - @Override - public float getValue(SplineOverScroller scroller) { - return scroller.mCurrentPosition; - } - - @Override - public void setValue(SplineOverScroller scroller, float value) { - scroller.mCurrentPosition = (int) value; - } - }; - - static { - float x_min = 0.0f; - float y_min = 0.0f; - for (int i = 0; i < NB_SAMPLES; i++) { - final float alpha = (float) i / NB_SAMPLES; - - float x_max = 1.0f; - float x, tx, coef; - while (true) { - x = x_min + (x_max - x_min) / 2.0f; - coef = 3.0f * x * (1.0f - x); - tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x; - if (Math.abs(tx - alpha) < 1E-5) break; - if (tx > alpha) x_max = x; - else x_min = x; - } - SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x; - - float y_max = 1.0f; - float y, dy; - while (true) { - y = y_min + (y_max - y_min) / 2.0f; - coef = 3.0f * y * (1.0f - y); - dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y; - if (Math.abs(dy - alpha) < 1E-5) break; - if (dy > alpha) y_max = y; - else y_min = y; - } - SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y; - } - SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; - } - - void setFriction(float friction) { - mFlingFriction = friction; - } - - SplineOverScroller(Context context) { - mContext = context; - mFinished = true; - final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; - mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2) - * 39.37f // inch/meter - * ppi - * 0.84f; // look and feel tuning - } - - void updateScroll(float q) { - if (mState == SPRING) { - return; - } - mCurrentPosition = mStart + Math.round(q * (mFinal - mStart)); - } - - /* - * Get a signed deceleration that will reduce the velocity. - */ - static private float getDeceleration(int velocity) { - return velocity > 0 ? -GRAVITY : GRAVITY; - } - - /* - * Modifies mDuration to the duration it takes to get from start to newFinal using the - * spline interpolation. The previous duration was needed to get to oldFinal. - */ - private void adjustDuration(int start, int oldFinal, int newFinal) { - final int oldDistance = oldFinal - start; - final int newDistance = newFinal - start; - final float x = Math.abs((float) newDistance / oldDistance); - final int index = (int) (NB_SAMPLES * x); - if (index < NB_SAMPLES) { - final float x_inf = (float) index / NB_SAMPLES; - final float x_sup = (float) (index + 1) / NB_SAMPLES; - final float t_inf = SPLINE_TIME[index]; - final float t_sup = SPLINE_TIME[index + 1]; - final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf); - mDuration *= timeCoef; - } - } - - void startScroll(int start, int distance, int duration) { - startScroll(start, distance, duration, 0); - } - - void startScroll(int start, int distance, int duration, float velocity) { - mFinished = false; - - mCurrentPosition = mStart = start; - mFinal = start + distance; - - mStartTime = AnimationUtils.currentAnimationTimeMillis(); - mDuration = duration; - - if (mSpring != null) { - mSpring.cancel(); - } - - if (mState == SPRING) { - mSpring = new SpringAnimation(this, SPRING_PROPERTY); - - ResourceProvider rp = DynamicResource.provider(mContext); - float stiffness = rp.getFloat(R.dimen.horizontal_spring_stiffness); - float damping = rp.getFloat(R.dimen.horizontal_spring_damping_ratio); - mSpring.setSpring(new SpringForce(mFinal) - .setStiffness(stiffness) - .setDampingRatio(damping)); - mSpring.setStartVelocity(velocity); - mSpring.animateToFinalPosition(mFinal); - mSpring.addEndListener((animation, canceled, value, velocity1) -> { - mSpring = null; - finish(); - mState = SPLINE; - }); - } - // Unused - mDeceleration = 0.0f; - mVelocity = 0; - } - - void finish() { - if (mSpring != null && mSpring.isRunning()) mSpring.cancel(); - - mCurrentPosition = mFinal; - // Not reset since WebView relies on this value for fast fling. - // TODO: restore when WebView uses the fast fling implemented in this class. - // mCurrVelocity = 0.0f; - mFinished = true; - } - - void setFinalPosition(int position) { - mFinal = position; - if (mState == SPRING && mSpring != null) { - mSpring.animateToFinalPosition(mFinal); - } - mSplineDistance = mFinal - mStart; - mFinished = false; - } - - void extendDuration(int extend) { - final long time = AnimationUtils.currentAnimationTimeMillis(); - final int elapsedTime = (int) (time - mStartTime); - mDuration = mSplineDuration = elapsedTime + extend; - mFinished = false; - } - - boolean springback(int start, int min, int max) { - mFinished = true; - - mCurrentPosition = mStart = mFinal = start; - mVelocity = 0; - - mStartTime = AnimationUtils.currentAnimationTimeMillis(); - mDuration = 0; - - if (start < min) { - startSpringback(start, min, 0); - } else if (start > max) { - startSpringback(start, max, 0); - } - - return !mFinished; - } - - private void startSpringback(int start, int end, int velocity) { - // mStartTime has been set - mFinished = false; - mState = CUBIC; - mCurrentPosition = mStart = start; - mFinal = end; - final int delta = start - end; - mDeceleration = getDeceleration(delta); - // TODO take velocity into account - mVelocity = -delta; // only sign is used - mOver = Math.abs(delta); - mDuration = (int) (1000.0 * Math.sqrt(-2.0 * delta / mDeceleration)); - } - - void fling(int start, int velocity, int min, int max, int over) { - mOver = over; - mFinished = false; - mCurrVelocity = mVelocity = velocity; - mDuration = mSplineDuration = 0; - mStartTime = AnimationUtils.currentAnimationTimeMillis(); - mCurrentPosition = mStart = start; - - if (start > max || start < min) { - startAfterEdge(start, min, max, velocity); - return; - } - - mState = SPLINE; - double totalDistance = 0.0; - - if (velocity != 0) { - mDuration = mSplineDuration = getSplineFlingDuration(velocity); - totalDistance = getSplineFlingDistance(velocity); - } - - mSplineDistance = (int) (totalDistance * Math.signum(velocity)); - mFinal = start + mSplineDistance; - - // Clamp to a valid final position - if (mFinal < min) { - adjustDuration(mStart, mFinal, min); - mFinal = min; - } - - if (mFinal > max) { - adjustDuration(mStart, mFinal, max); - mFinal = max; - } - } - - private double getSplineDeceleration(int velocity) { - return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff)); - } - - private double getSplineFlingDistance(int velocity) { - final double l = getSplineDeceleration(velocity); - final double decelMinusOne = DECELERATION_RATE - 1.0; - return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l); - } - - /* Returns the duration, expressed in milliseconds */ - private int getSplineFlingDuration(int velocity) { - final double l = getSplineDeceleration(velocity); - final double decelMinusOne = DECELERATION_RATE - 1.0; - return (int) (1000.0 * Math.exp(l / decelMinusOne)); - } - - private void fitOnBounceCurve(int start, int end, int velocity) { - // Simulate a bounce that started from edge - final float durationToApex = - velocity / mDeceleration; - // The float cast below is necessary to avoid integer overflow. - final float velocitySquared = (float) velocity * velocity; - final float distanceToApex = velocitySquared / 2.0f / Math.abs(mDeceleration); - final float distanceToEdge = Math.abs(end - start); - final float totalDuration = (float) Math.sqrt( - 2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration)); - mStartTime -= (int) (1000.0f * (totalDuration - durationToApex)); - mCurrentPosition = mStart = end; - mVelocity = (int) (- mDeceleration * totalDuration); - } - - private void startBounceAfterEdge(int start, int end, int velocity) { - mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity); - fitOnBounceCurve(start, end, velocity); - onEdgeReached(); - } - - private void startAfterEdge(int start, int min, int max, int velocity) { - if (start > min && start < max) { - Log.e("OverScroller", "startAfterEdge called from a valid position"); - mFinished = true; - return; - } - final boolean positive = start > max; - final int edge = positive ? max : min; - final int overDistance = start - edge; - boolean keepIncreasing = overDistance * velocity >= 0; - if (keepIncreasing) { - // Will result in a bounce or a to_boundary depending on velocity. - startBounceAfterEdge(start, edge, velocity); - } else { - final double totalDistance = getSplineFlingDistance(velocity); - if (totalDistance > Math.abs(overDistance)) { - fling(start, velocity, positive ? min : start, positive ? start : max, mOver); - } else { - startSpringback(start, edge, velocity); - } - } - } - - void notifyEdgeReached(int start, int end, int over) { - // mState is used to detect successive notifications - if (mState == SPLINE) { - mOver = over; - mStartTime = AnimationUtils.currentAnimationTimeMillis(); - // We were in fling/scroll mode before: current velocity is such that distance to - // edge is increasing. This ensures that startAfterEdge will not start a new fling. - startAfterEdge(start, end, end, (int) mCurrVelocity); - } - } - - private void onEdgeReached() { - // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached. - // The float cast below is necessary to avoid integer overflow. - final float velocitySquared = (float) mVelocity * mVelocity; - float distance = velocitySquared / (2.0f * Math.abs(mDeceleration)); - final float sign = Math.signum(mVelocity); - - if (distance > mOver) { - // Default deceleration is not sufficient to slow us down before boundary - mDeceleration = - sign * velocitySquared / (2.0f * mOver); - distance = mOver; - } - - mOver = (int) distance; - mState = BALLISTIC; - mFinal = mStart + (int) (mVelocity > 0 ? distance : -distance); - mDuration = - (int) (1000.0f * mVelocity / mDeceleration); - } - - boolean continueWhenFinished() { - switch (mState) { - case SPLINE: - // Duration from start to null velocity - if (mDuration < mSplineDuration) { - // If the animation was clamped, we reached the edge - mCurrentPosition = mStart = mFinal; - // TODO Better compute speed when edge was reached - mVelocity = (int) mCurrVelocity; - mDeceleration = getDeceleration(mVelocity); - mStartTime += mDuration; - onEdgeReached(); - } else { - // Normal stop, no need to continue - return false; - } - break; - case BALLISTIC: - mStartTime += mDuration; - startSpringback(mFinal, mStart, 0); - break; - case CUBIC: - return false; - } - - update(); - return true; - } - - /* - * Update the current position and velocity for current time. Returns - * true if update has been done and false if animation duration has been - * reached. - */ - boolean update() { - if (mState == SPRING) { - return mFinished; - } - - final long time = AnimationUtils.currentAnimationTimeMillis(); - final long currentTime = time - mStartTime; - - if (currentTime == 0) { - // Skip work but report that we're still going if we have a nonzero duration. - return mDuration > 0; - } - if (currentTime > mDuration) { - return false; - } - - double distance = 0.0; - switch (mState) { - case SPLINE: { - final float t = (float) currentTime / mSplineDuration; - final int index = (int) (NB_SAMPLES * t); - float distanceCoef = 1.f; - float velocityCoef = 0.f; - if (index < NB_SAMPLES) { - final float t_inf = (float) index / NB_SAMPLES; - final float t_sup = (float) (index + 1) / NB_SAMPLES; - final float d_inf = SPLINE_POSITION[index]; - final float d_sup = SPLINE_POSITION[index + 1]; - velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); - distanceCoef = d_inf + (t - t_inf) * velocityCoef; - } - - distance = distanceCoef * mSplineDistance; - mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f; - break; - } - - case BALLISTIC: { - final float t = currentTime / 1000.0f; - mCurrVelocity = mVelocity + mDeceleration * t; - distance = mVelocity * t + mDeceleration * t * t / 2.0f; - break; - } - - case CUBIC: { - final float t = (float) (currentTime) / mDuration; - final float t2 = t * t; - final float sign = Math.signum(mVelocity); - distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2); - mCurrVelocity = sign * mOver * 6.0f * (- t + t2); - break; - } - } - - mCurrentPosition = mStart + (int) Math.round(distance); - - return true; - } - } -} \ No newline at end of file diff --git a/src/com/android/launcher3/util/OverlayEdgeEffect.java b/src/com/android/launcher3/util/OverlayEdgeEffect.java new file mode 100644 index 0000000000..8d455c6afe --- /dev/null +++ b/src/com/android/launcher3/util/OverlayEdgeEffect.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 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.util; + +import android.content.Context; +import android.graphics.Canvas; +import android.widget.EdgeEffect; + +import com.android.launcher3.Utilities; +import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay; + +/** + * Extension of {@link EdgeEffect} which shows the Launcher overlay + */ +public class OverlayEdgeEffect extends EdgeEffectCompat { + + private final LauncherOverlay mOverlay; + private final boolean mIsRtl; + + private float mDistance; + private boolean mIsScrolling; + + public OverlayEdgeEffect(Context context, LauncherOverlay overlay) { + super(context); + mOverlay = overlay; + mIsRtl = Utilities.isRtl(context.getResources()); + + } + + @Override + public float getDistance() { + return mDistance; + } + + public float onPullDistance(float deltaDistance, float displacement) { + mDistance = Math.max(0f, deltaDistance + mDistance); + if (!mIsScrolling) { + mOverlay.onScrollInteractionBegin(); + mIsScrolling = true; + } + mOverlay.onScrollChange(mDistance, mIsRtl); + return mDistance > 0 ? deltaDistance : 0; + } + + @Override + public void onAbsorb(int velocity) { } + + @Override + public boolean isFinished() { + return mDistance <= 0; + } + + @Override + public void onRelease() { + if (mIsScrolling) { + mDistance = 0; + mOverlay.onScrollInteractionEnd(); + mIsScrolling = false; + } + } + + @Override + public boolean draw(Canvas canvas) { + return false; + } + + public void finish() { + mDistance = 0; + } +} diff --git a/src/com/android/launcher3/util/TranslateEdgeEffect.java b/src/com/android/launcher3/util/TranslateEdgeEffect.java new file mode 100644 index 0000000000..8fdc8df347 --- /dev/null +++ b/src/com/android/launcher3/util/TranslateEdgeEffect.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.util; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.RenderNode; +import android.widget.EdgeEffect; + +/** + * Extension of {@link EdgeEffect} which translates the content instead of the default + * platform implementation + */ +@SuppressWarnings("NewApi") +public class TranslateEdgeEffect extends EdgeEffectCompat { + + private final RenderNode mNode; + + public TranslateEdgeEffect(Context context) { + super(context); + mNode = new RenderNode("TranslateEdgeEffect"); + } + + @Override + public boolean draw(Canvas canvas) { + return false; + } + + public boolean getTranslationShift(float[] out) { + Canvas c = mNode.beginRecording(1, 1); + boolean result = super.draw(c); + mNode.endRecording(); + + out[0] = getDistance(); + return result; + } +}