mirror of
https://github.com/status-im/react-native.git
synced 2025-01-27 09:45:04 +00:00
Fix ScrollView bounce back bug in open source
Summary: We now reach in and use the Scroller directly, reimplementing fling() and onOverScrolled(). I verified that in Android 4.1.2 ScrollView#mScroller exists as a private on ScrollView, but there's still potential that this could break things if OEMs have modified ScrollView so we just log a warning if we can't get access to that field. Reviewed By: foghina Differential Revision: D3650008 fbshipit-source-id: e52909bf9d6008f6d1ecd458aee25fe82ffaac19
This commit is contained in:
parent
7cf4e3665a
commit
36ca1a078a
@ -11,16 +11,21 @@ package com.facebook.react.views.scroll;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.MeasureSpecAssertions;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.views.view.ReactClippingViewGroup;
|
||||
@ -36,7 +41,11 @@ import com.facebook.infer.annotation.Assertions;
|
||||
*/
|
||||
public class ReactScrollView extends ScrollView implements ReactClippingViewGroup {
|
||||
|
||||
private static Field sScrollerField;
|
||||
private static boolean sTriedToGetScrollerField = false;
|
||||
|
||||
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
|
||||
private final OverScroller mScroller;
|
||||
|
||||
private @Nullable Rect mClippingRect;
|
||||
private boolean mDoneFlinging;
|
||||
@ -57,6 +66,29 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
public ReactScrollView(Context context, @Nullable FpsListener fpsListener) {
|
||||
super(context);
|
||||
mFpsListener = fpsListener;
|
||||
|
||||
if (!sTriedToGetScrollerField) {
|
||||
sTriedToGetScrollerField = true;
|
||||
try {
|
||||
sScrollerField = ScrollView.class.getDeclaredField("mScroller");
|
||||
sScrollerField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Log.w(
|
||||
ReactConstants.TAG,
|
||||
"Failed to get mScroller field for ScrollView! " +
|
||||
"This app will exhibit the bounce-back scrolling bug :(");
|
||||
}
|
||||
}
|
||||
|
||||
if (sScrollerField != null) {
|
||||
try {
|
||||
mScroller = (OverScroller) sScrollerField.get(this);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Failed to get mScroller from ScrollView!", e);
|
||||
}
|
||||
} else {
|
||||
mScroller = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSendMomentumEvents(boolean sendMomentumEvents) {
|
||||
@ -187,7 +219,36 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
|
||||
@Override
|
||||
public void fling(int velocityY) {
|
||||
super.fling(velocityY);
|
||||
if (mScroller != null) {
|
||||
// FB SCROLLVIEW CHANGE
|
||||
|
||||
// We provide our own version of fling that uses a different call to the standard OverScroller
|
||||
// which takes into account the possibility of adding new content while the ScrollView is
|
||||
// animating. Because we give essentially no max Y for the fling, the fling will continue as long
|
||||
// as there is content. See #onOverScrolled() to see the second part of this change which properly
|
||||
// aborts the scroller animation when we get to the bottom of the ScrollView content.
|
||||
|
||||
int scrollWindowHeight = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||
|
||||
mScroller.fling(
|
||||
getScrollX(),
|
||||
getScrollY(),
|
||||
0,
|
||||
velocityY,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Integer.MAX_VALUE,
|
||||
0,
|
||||
scrollWindowHeight / 2);
|
||||
|
||||
postInvalidateOnAnimation();
|
||||
|
||||
// END FB SCROLLVIEW CHANGE
|
||||
} else {
|
||||
super.fling(velocityY);
|
||||
}
|
||||
|
||||
if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) {
|
||||
mFlinging = true;
|
||||
enableFpsListener();
|
||||
@ -247,4 +308,27 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
mEndBackground = new ColorDrawable(mEndFillColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
|
||||
if (mScroller != null) {
|
||||
// FB SCROLLVIEW CHANGE
|
||||
|
||||
// This is part two of the reimplementation of fling to fix the bounce-back bug. See #fling() for
|
||||
// more information.
|
||||
|
||||
if (!mScroller.isFinished() && mScroller.getCurrY() != mScroller.getFinalY()) {
|
||||
int scrollRange = Math.max(
|
||||
0,
|
||||
getChildAt(0).getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
|
||||
if (scrollY >= scrollRange) {
|
||||
mScroller.abortAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// END FB SCROLLVIEW CHANGE
|
||||
}
|
||||
|
||||
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user