Skip to content

Commit b9b23e1

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Fix incorrect platform auto-scroll to right when height changes
Summary: When the height of a HorizontalScrollView changes and there is a `layout` event, it can cause the underlying platform View code to scroll slightly to the right. I'm... not really sure why, even after looking at the View code for a while. But it is clearly detectable and mirrors issues with RTL that were fixed recently. This might warrant more investigation, but I believe the fix is relatively safe - we detect if there's an autoscroll only if the height changes and only if the scroll happens in "layout". That scopes the hack pretty well to just this bug. There aren't really times when we actually want layout to scroll to the right, so... I think this is reasonable. Changelog: [Changed][Android] Fixed issue that causes HorizontalScrollView to shift to the right when a TextInput is selected and keyboard pops up Reviewed By: mdvacca Differential Revision: D26972710 fbshipit-source-id: 441b1a3f07b9b68195a9e5e9a0c8d75c9d24a109
1 parent 310a6bc commit b9b23e1

File tree

1 file changed

+36
-0
lines changed

1 file changed

+36
-0
lines changed

ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java

+36
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,16 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
5454
private static boolean DEBUG_MODE = false && ReactBuildConfig.DEBUG;
5555
private static String TAG = ReactHorizontalScrollView.class.getSimpleName();
5656

57+
private static int NO_SCROLL_POSITION = Integer.MIN_VALUE;
58+
5759
private static @Nullable Field sScrollerField;
5860
private static boolean sTriedToGetScrollerField = false;
5961
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
6062
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
6163
private int mLayoutDirection;
6264

65+
private int mScrollXAfterMeasure = NO_SCROLL_POSITION;
66+
6367
private static final int UNSET_CONTENT_OFFSET = -1;
6468

6569
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
@@ -275,7 +279,15 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
275279
measuredHeight);
276280
}
277281

282+
boolean measuredHeightChanged = getMeasuredHeight() != measuredHeight;
283+
278284
setMeasuredDimension(measuredWidth, measuredHeight);
285+
286+
// See how `mScrollXAfterMeasure` is used in `onLayout`, and why we only enable the
287+
// hack if the height has changed.
288+
if (measuredHeightChanged && mScroller != null) {
289+
mScrollXAfterMeasure = mScroller.getCurrX();
290+
}
279291
}
280292

281293
@Override
@@ -284,6 +296,30 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
284296
FLog.i(TAG, "onLayout[%d] l %d t %d r %d b %d", getId(), l, t, r, b);
285297
}
286298

299+
// Has the scrollX changed between the last onMeasure and this layout?
300+
// If so, cancel the animation.
301+
// Essentially, if the height changes (due to keyboard popping up, for instance) the
302+
// underlying View.layout method will in some cases scroll to an incorrect X position -
303+
// see also the hacks in `fling`. The order of layout is called in the order of: onMeasure,
304+
// layout, onLayout.
305+
// We cannot override `layout` but we can detect the sequence of events between onMeasure
306+
// and onLayout.
307+
if (mScrollXAfterMeasure != NO_SCROLL_POSITION
308+
&& mScroller != null
309+
&& mScrollXAfterMeasure != mScroller.getFinalX()
310+
&& !mScroller.isFinished()) {
311+
if (DEBUG_MODE) {
312+
FLog.i(
313+
TAG,
314+
"onLayout[%d] scroll hack enabled: reset to previous scrollX position of %d",
315+
getId(),
316+
mScrollXAfterMeasure);
317+
}
318+
mScroller.startScroll(mScrollXAfterMeasure, mScroller.getFinalY(), 0, 0);
319+
mScroller.forceFinished(true);
320+
mScrollXAfterMeasure = NO_SCROLL_POSITION;
321+
}
322+
287323
// Call with the present values in order to re-layout if necessary
288324
// If a "pending" value has been set, we restore that value.
289325
// That value gets cleared by reactScrollTo.

0 commit comments

Comments
 (0)