diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 86bfc939046104..3c38d92dfabcad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -21,6 +21,7 @@ import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.OverScroller; import android.widget.ScrollView; @@ -39,7 +40,7 @@ *

ReactScrollView only supports vertical scrolling. For horizontal scrolling, * use {@link ReactHorizontalScrollView}. */ -public class ReactScrollView extends ScrollView implements ReactClippingViewGroup { +public class ReactScrollView extends ScrollView implements ReactClippingViewGroup, ViewGroup.OnHierarchyChangeListener, View.OnLayoutChangeListener { private static Field sScrollerField; private static boolean sTriedToGetScrollerField = false; @@ -58,6 +59,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou private @Nullable String mScrollPerfTag; private @Nullable Drawable mEndBackground; private int mEndFillColor = Color.TRANSPARENT; + private View mContentView; public ReactScrollView(ReactContext context) { this(context, null); @@ -98,6 +100,8 @@ public ReactScrollView(ReactContext context, @Nullable FpsListener fpsListener) } else { mScroller = null; } + + setOnHierarchyChangeListener(this); } public void setSendMomentumEvents(boolean sendMomentumEvents) { @@ -299,6 +303,12 @@ private boolean isScrollPerfLoggingEnabled() { return mFpsListener != null && mScrollPerfTag != null && !mScrollPerfTag.isEmpty(); } + private int getMaxScrollY() { + int contentHeight = mContentView.getHeight(); + int viewportHeight = getHeight() - getPaddingBottom() - getPaddingTop(); + return Math.max(0, contentHeight - viewportHeight); + } + @Override public void draw(Canvas canvas) { if (mEndFillColor != Color.TRANSPARENT) { @@ -327,9 +337,7 @@ protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolea // more information. if (!mScroller.isFinished() && mScroller.getCurrY() != mScroller.getFinalY()) { - int scrollRange = Math.max( - 0, - getChildAt(0).getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop())); + int scrollRange = getMaxScrollY(); if (scrollY >= scrollRange) { mScroller.abortAnimation(); scrollY = scrollRange; @@ -341,4 +349,35 @@ protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolea super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); } + + @Override + public void onChildViewAdded(View parent, View child) { + mContentView = child; + mContentView.addOnLayoutChangeListener(this); + } + + @Override + public void onChildViewRemoved(View parent, View child) { + mContentView.removeOnLayoutChangeListener(this); + mContentView = null; + } + + /** + * Called when a mContentView's layout has changed. Fixes the scroll position if it's too large + * after the content resizes. Without this, the user would see a blank ScrollView when the scroll + * position is larger than the ScrollView's max scroll position after the content shrinks. + */ + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mContentView == null) { + return; + } + + int currentScrollY = getScrollY(); + int maxScrollY = getMaxScrollY(); + if (currentScrollY > maxScrollY) { + scrollTo(getScrollX(), maxScrollY); + } + } } +