Merge "Support fling back in scrolling containers"

This commit is contained in:
TreeHugger Robot
2022-06-28 23:03:09 +00:00
committed by Android (Google) Code Review
4 changed files with 239 additions and 13 deletions

View File

@@ -241,6 +241,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
/**
* When flinging the stretch towards scrolling content, it should destretch quicker than the
* fling would normally do. The visual effect of flinging the stretch looks strange as little
* appears to happen at first and then when the stretch disappears, the content starts
* scrolling quickly.
*/
private static final float FLING_DESTRETCH_FACTOR = 4f;
/**
* The thread that created this view.
*/
@@ -4216,9 +4224,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// fling further.
boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
if (flingVelocity && !mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onAbsorb(initialVelocity);
if (shouldAbsorb(mEdgeGlowTop, initialVelocity)) {
mEdgeGlowTop.onAbsorb(initialVelocity);
} else {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
mFlingRunnable.start(-initialVelocity);
}
} else if (flingVelocity && !mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onAbsorb(-initialVelocity);
if (shouldAbsorb(mEdgeGlowBottom, -initialVelocity)) {
mEdgeGlowBottom.onAbsorb(-initialVelocity);
} else {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
mFlingRunnable.start(-initialVelocity);
}
} else if (flingVelocity
&& !((mFirstPosition == 0
&& firstChildTop == contentTop - mOverscrollDistance)
@@ -4301,6 +4323,60 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
/**
* Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
* animate with a fling. It will animate with a fling if the velocity will remove the
* EdgeEffect through its normal operation.
*
* @param edgeEffect The EdgeEffect that might absorb the velocity.
* @param velocity The velocity of the fling motion
* @return true if the velocity should be absorbed or false if it should be flung.
*/
private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
if (velocity > 0) {
return true;
}
float distance = edgeEffect.getDistance() * getHeight();
// This is flinging without the spring, so let's see if it will fling past the overscroll
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
float flingDistance = mFlingRunnable.getSplineFlingDistance(-velocity);
return flingDistance < distance;
}
/**
* Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
* consuming deltas from EdgeEffects
* @param unconsumed The unconsumed delta that the EdgeEffets may consume
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
if (unconsumed < 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
* mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowTop.finish();
}
return unconsumed - consumed;
}
if (unconsumed > 0 && mEdgeGlowBottom != null && mEdgeGlowBottom.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
* mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowBottom.finish();
}
return unconsumed - consumed;
}
return unconsumed;
}
private boolean shouldDisplayEdgeEffects() {
return getOverScrollMode() != OVER_SCROLL_NEVER;
}
@@ -4783,6 +4859,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mScroller = new OverScroller(getContext());
}
float getSplineFlingDistance(int velocity) {
return (float) mScroller.getSplineFlingDistance(velocity);
}
// Use AbsListView#fling(int) instead
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
void start(int initialVelocity) {
@@ -4905,6 +4985,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (mItemCount == 0 || getChildCount() == 0) {
mEdgeGlowBottom.onRelease();
mEdgeGlowTop.onRelease();
endFling();
return;
}
@@ -4915,7 +4997,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Flip sign to convert finger direction to list items direction
// (e.g. finger moving down means list is moving towards the top)
int delta = mLastFlingY - y;
int delta = consumeFlingInStretch(mLastFlingY - y);
// Pretend that each frame of a fling scroll is a touch scroll
if (delta > 0) {

View File

@@ -77,6 +77,14 @@ public class HorizontalScrollView extends FrameLayout {
private static final String TAG = "HorizontalScrollView";
/**
* When flinging the stretch towards scrolling content, it should destretch quicker than the
* fling would normally do. The visual effect of flinging the stretch looks strange as little
* appears to happen at first and then when the stretch disappears, the content starts
* scrolling quickly.
*/
private static final float FLING_DESTRETCH_FACTOR = 4f;
private long mLastScroll;
private final Rect mTempRect = new Rect();
@@ -1456,18 +1464,19 @@ public class HorizontalScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
int deltaX = consumeFlingInStretch(x - oldX);
if (oldX != x || oldY != y) {
if (deltaX != 0 || oldY != y) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
overScrollBy(x - oldX, y - oldY, oldX, oldY, range, 0,
overScrollBy(deltaX, y - oldY, oldX, oldY, range, 0,
mOverflingDistance, 0, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (canOverscroll) {
if (canOverscroll && deltaX != 0) {
if (x < 0 && oldX >= 0) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
} else if (x > range && oldX <= range) {
@@ -1482,6 +1491,36 @@ public class HorizontalScrollView extends FrameLayout {
}
}
/**
* Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
* consuming deltas from EdgeEffects
* @param unconsumed The unconsumed delta that the EdgeEffets may consume
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
int size = getWidth();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
* mEdgeGlowLeft.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowLeft.finish();
}
return unconsumed - consumed;
}
if (unconsumed < 0 && mEdgeGlowRight != null && mEdgeGlowRight.getDistance() != 0f) {
int size = getWidth();
float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
* mEdgeGlowRight.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowRight.finish();
}
return unconsumed - consumed;
}
return unconsumed;
}
/**
* Scrolls the view to the given child.
*
@@ -1746,11 +1785,23 @@ public class HorizontalScrollView extends FrameLayout {
int maxScroll = Math.max(0, right - width);
boolean shouldFling = false;
if (mScrollX == 0 && !mEdgeGlowLeft.isFinished()) {
mEdgeGlowLeft.onAbsorb(-velocityX);
if (shouldAbsorb(mEdgeGlowLeft, -velocityX)) {
mEdgeGlowLeft.onAbsorb(-velocityX);
} else {
shouldFling = true;
}
} else if (mScrollX == maxScroll && !mEdgeGlowRight.isFinished()) {
mEdgeGlowRight.onAbsorb(velocityX);
if (shouldAbsorb(mEdgeGlowRight, velocityX)) {
mEdgeGlowRight.onAbsorb(velocityX);
} else {
shouldFling = true;
}
} else {
shouldFling = true;
}
if (shouldFling) {
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
maxScroll, 0, 0, width / 2, 0);
@@ -1773,6 +1824,27 @@ public class HorizontalScrollView extends FrameLayout {
}
}
/**
* Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
* animate with a fling. It will animate with a fling if the velocity will remove the
* EdgeEffect through its normal operation.
*
* @param edgeEffect The EdgeEffect that might absorb the velocity.
* @param velocity The velocity of the fling motion
* @return true if the velocity should be absorbed or false if it should be flung.
*/
private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
if (velocity > 0) {
return true;
}
float distance = edgeEffect.getDistance() * getWidth();
// This is flinging without the spring, so let's see if it will fling past the overscroll
float flingDistance = (float) mScroller.getSplineFlingDistance(-velocity);
return flingDistance < distance;
}
/**
* {@inheritDoc}
*

View File

@@ -527,6 +527,10 @@ public class OverScroller {
Math.signum(yvel) == Math.signum(dy);
}
double getSplineFlingDistance(int velocity) {
return mScrollerY.getSplineFlingDistance(velocity);
}
static class SplineOverScroller {
// Initial position
private int mStart;

View File

@@ -85,6 +85,14 @@ public class ScrollView extends FrameLayout {
private static final String TAG = "ScrollView";
/**
* When flinging the stretch towards scrolling content, it should destretch quicker than the
* fling would normally do. The visual effect of flinging the stretch looks strange as little
* appears to happen at first and then when the stretch disappears, the content starts
* scrolling quickly.
*/
private static final float FLING_DESTRETCH_FACTOR = 4f;
@UnsupportedAppUsage
private long mLastScroll;
@@ -1488,18 +1496,19 @@ public class ScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
int deltaY = consumeFlingInStretch(y - oldY);
if (oldX != x || oldY != y) {
if (oldX != x || deltaY != 0) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
overScrollBy(x - oldX, deltaY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (canOverscroll) {
if (canOverscroll && deltaY != 0) {
if (y < 0 && oldY >= 0) {
mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
} else if (y > range && oldY <= range) {
@@ -1520,6 +1529,36 @@ public class ScrollView extends FrameLayout {
}
}
/**
* Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
* consuming deltas from EdgeEffects
* @param unconsumed The unconsumed delta that the EdgeEffets may consume
* @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
*/
private int consumeFlingInStretch(int unconsumed) {
if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
* mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowTop.finish();
}
return unconsumed - consumed;
}
if (unconsumed < 0 && mEdgeGlowBottom != null && mEdgeGlowBottom.getDistance() != 0f) {
int size = getHeight();
float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
* mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f));
if (consumed != unconsumed) {
mEdgeGlowBottom.finish();
}
return unconsumed - consumed;
}
return unconsumed;
}
/**
* Scrolls the view to the given child.
*
@@ -1803,14 +1842,43 @@ public class ScrollView extends FrameLayout {
fling(velocityY);
} else if (!consumed) {
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onAbsorb(-velocityY);
if (shouldAbsorb(mEdgeGlowTop, -velocityY)) {
mEdgeGlowTop.onAbsorb(-velocityY);
} else {
fling(velocityY);
}
} else if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onAbsorb(velocityY);
if (shouldAbsorb(mEdgeGlowBottom, velocityY)) {
mEdgeGlowBottom.onAbsorb(velocityY);
} else {
fling(velocityY);
}
}
}
}
}
/**
* Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
* animate with a fling. It will animate with a fling if the velocity will remove the
* EdgeEffect through its normal operation.
*
* @param edgeEffect The EdgeEffect that might absorb the velocity.
* @param velocity The velocity of the fling motion
* @return true if the velocity should be absorbed or false if it should be flung.
*/
private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
if (velocity > 0) {
return true;
}
float distance = edgeEffect.getDistance() * getHeight();
// This is flinging without the spring, so let's see if it will fling past the overscroll
float flingDistance = (float) mScroller.getSplineFlingDistance(-velocity);
return flingDistance < distance;
}
@UnsupportedAppUsage
private void endDrag() {
mIsBeingDragged = false;