From 02843213c076f4ea754dbd9edf459500437adc06 Mon Sep 17 00:00:00 2001 From: George Mount Date: Thu, 29 Apr 2021 18:19:15 +0000 Subject: [PATCH] Fling while overscrolled: ScrollView/HorizontalScrollView Bug: 186430321 ScrollView and HorizontalScrollView was not allowing a fling effect while overscrolling. This adds the ability to fling while overscrolling. Test: new test Test: manual ApiDemos Change-Id: Iebd402ff0f5b6f16518ecc5403e085cb58de5976 --- .../android/widget/HorizontalScrollView.java | 48 +++++++++++-------- core/java/android/widget/ScrollView.java | 17 +++++-- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index b5a58481ae2aa..e41893e37103e 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -47,6 +47,7 @@ import android.view.animation.AnimationUtils; import android.view.inspector.InspectableProperty; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -86,19 +87,23 @@ public class HorizontalScrollView extends FrameLayout { * * Even though this field is practically final, we cannot make it final because there are apps * setting it via reflection and they need to keep working until they target Q. + * @hide */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124053130) - private EdgeEffect mEdgeGlowLeft; + @VisibleForTesting + public EdgeEffect mEdgeGlowLeft; /** * Tracks the state of the bottom edge glow. * * Even though this field is practically final, we cannot make it final because there are apps * setting it via reflection and they need to keep working until they target Q. + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052619) - private EdgeEffect mEdgeGlowRight; + @VisibleForTesting + public EdgeEffect mEdgeGlowRight; /** * Position of the last motion event. @@ -774,11 +779,8 @@ public class HorizontalScrollView extends FrameLayout { // Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. - if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0, - mOverscrollDistance, 0, true)) { - // Break our velocity if we hit a scroll barrier. - mVelocityTracker.clear(); - } + overScrollBy(deltaX, 0, mScrollX, 0, range, 0, + mOverscrollDistance, 0, true); if (canOverscroll && deltaX != 0f) { final int pulledToX = oldX + deltaX; @@ -1737,23 +1739,31 @@ public class HorizontalScrollView extends FrameLayout { public void fling(int velocityX) { if (getChildCount() > 0) { int width = getWidth() - mPaddingRight - mPaddingLeft; - int right = getChildAt(0).getWidth(); + int right = getChildAt(0).getRight() - mPaddingLeft; - mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, - Math.max(0, right - width), 0, 0, width/2, 0); + int maxScroll = Math.max(0, right - width); - final boolean movingRight = velocityX > 0; + if (mScrollX == 0 && !mEdgeGlowLeft.isFinished()) { + mEdgeGlowLeft.onAbsorb(-velocityX); + } else if (mScrollX == maxScroll && !mEdgeGlowRight.isFinished()) { + mEdgeGlowRight.onAbsorb(velocityX); + } else { + mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, + maxScroll, 0, 0, width / 2, 0); - View currentFocused = findFocus(); - View newFocused = findFocusableViewInMyBounds(movingRight, - mScroller.getFinalX(), currentFocused); + final boolean movingRight = velocityX > 0; - if (newFocused == null) { - newFocused = this; - } + View currentFocused = findFocus(); + View newFocused = findFocusableViewInMyBounds(movingRight, + mScroller.getFinalX(), currentFocused); - if (newFocused != currentFocused) { - newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT); + if (newFocused == null) { + newFocused = this; + } + + if (newFocused != currentFocused) { + newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT); + } } postInvalidateOnAnimation(); diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 2dd7f022ff449..3610eb47edbca 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -49,6 +49,7 @@ import android.view.animation.AnimationUtils; import android.view.inspector.InspectableProperty; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -95,20 +96,24 @@ public class ScrollView extends FrameLayout { * * Even though this field is practically final, we cannot make it final because there are apps * setting it via reflection and they need to keep working until they target Q. + * @hide */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768600) - private EdgeEffect mEdgeGlowTop; + @VisibleForTesting + public EdgeEffect mEdgeGlowTop; /** * Tracks the state of the bottom edge glow. * * Even though this field is practically final, we cannot make it final because there are apps * setting it via reflection and they need to keep working until they target Q. + * @hide */ @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769386) - private EdgeEffect mEdgeGlowBottom; + @VisibleForTesting + public EdgeEffect mEdgeGlowBottom; /** * Position of the last motion event. @@ -1791,9 +1796,15 @@ public class ScrollView extends FrameLayout { final boolean canFling = (mScrollY > 0 || velocityY > 0) && (mScrollY < getScrollRange() || velocityY < 0); if (!dispatchNestedPreFling(0, velocityY)) { - dispatchNestedFling(0, velocityY, canFling); + final boolean consumed = dispatchNestedFling(0, velocityY, canFling); if (canFling) { fling(velocityY); + } else if (!consumed) { + if (!mEdgeGlowTop.isFinished()) { + mEdgeGlowTop.onAbsorb(-velocityY); + } else if (!mEdgeGlowBottom.isFinished()) { + mEdgeGlowBottom.onAbsorb(velocityY); + } } } }