Skip to content

Commit ed29ba1

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Support contentOffset property in Android's ScrollView and HorizontalScrollView
Summary: For a very long time, iOS has supported the `contentOffset` property but Android has not: #6849 This property can be used, primarily, to autoscroll the ScrollView to a starting position when it is first rendered, to avoid "jumps" that occur by asynchronously scrolling to a start position. Changelog: [Android][Changed] ScrollView now supports `contentOffset` Reviewed By: mdvacca Differential Revision: D21198236 fbshipit-source-id: 2b0773569ba42120cb1fcf0f3847ca98af2285e7
1 parent bda8aae commit ed29ba1

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const AndroidHorizontalScrollViewViewConfig = {
3737
snapToInterval: true,
3838
snapToStart: true,
3939
snapToOffsets: true,
40+
contentOffset: true,
4041
},
4142
};
4243

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

+31-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
5050
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
5151
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
5252

53+
private static final int UNSET_CONTENT_OFFSET = -1;
54+
5355
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
5456
private final @Nullable OverScroller mScroller;
5557
private final VelocityHelper mVelocityHelper = new VelocityHelper();
@@ -76,6 +78,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
7678
private boolean mSnapToEnd = true;
7779
private ReactViewBackgroundManager mReactBackgroundManager;
7880
private boolean mPagedArrowScrolling = false;
81+
private int pendingContentOffsetX = UNSET_CONTENT_OFFSET;
82+
private int pendingContentOffsetY = UNSET_CONTENT_OFFSET;
7983
private @Nullable StateWrapper mStateWrapper;
8084

8185
private final Rect mTempRect = new Rect();
@@ -224,7 +228,13 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
224228
@Override
225229
protected void onLayout(boolean changed, int l, int t, int r, int b) {
226230
// Call with the present values in order to re-layout if necessary
227-
reactScrollTo(getScrollX(), getScrollY());
231+
// If a "pending" value has been set, we restore that value.
232+
// That value gets cleared by reactScrollTo.
233+
int scrollToX =
234+
pendingContentOffsetX != UNSET_CONTENT_OFFSET ? pendingContentOffsetX : getScrollX();
235+
int scrollToY =
236+
pendingContentOffsetY != UNSET_CONTENT_OFFSET ? pendingContentOffsetY : getScrollY();
237+
reactScrollTo(scrollToX, scrollToY);
228238
}
229239

230240
/**
@@ -906,6 +916,7 @@ public void setBorderStyle(@Nullable String style) {
906916
public void reactSmoothScrollTo(int x, int y) {
907917
smoothScrollTo(x, y);
908918
updateStateOnScroll(x, y);
919+
setPendingContentOffsets(x, y);
909920
}
910921

911922
/**
@@ -917,6 +928,25 @@ public void reactSmoothScrollTo(int x, int y) {
917928
public void reactScrollTo(int x, int y) {
918929
scrollTo(x, y);
919930
updateStateOnScroll(x, y);
931+
setPendingContentOffsets(x, y);
932+
}
933+
934+
/**
935+
* If contentOffset is set before the View has been laid out, store the values and set them when
936+
* `onLayout` is called.
937+
*
938+
* @param x
939+
* @param y
940+
*/
941+
private void setPendingContentOffsets(int x, int y) {
942+
View child = getChildAt(0);
943+
if (child != null && child.getWidth() != 0 && child.getHeight() != 0) {
944+
pendingContentOffsetX = UNSET_CONTENT_OFFSET;
945+
pendingContentOffsetY = UNSET_CONTENT_OFFSET;
946+
} else {
947+
pendingContentOffsetX = x;
948+
pendingContentOffsetY = y;
949+
}
920950
}
921951

922952
public void updateState(@Nullable StateWrapper stateWrapper) {

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

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import androidx.annotation.Nullable;
1313
import androidx.core.view.ViewCompat;
1414
import com.facebook.react.bridge.ReadableArray;
15+
import com.facebook.react.bridge.ReadableMap;
1516
import com.facebook.react.module.annotations.ReactModule;
1617
import com.facebook.react.uimanager.DisplayMetricsHolder;
1718
import com.facebook.react.uimanager.PixelUtil;
@@ -299,4 +300,11 @@ public void setFadingEdgeLength(ReactHorizontalScrollView view, int value) {
299300
view.setFadingEdgeLength(0);
300301
}
301302
}
303+
304+
@ReactProp(name = "contentOffset")
305+
public void setContentOffset(ReactHorizontalScrollView view, ReadableMap value) {
306+
double x = value.getDouble("x");
307+
double y = value.getDouble("y");
308+
view.reactScrollTo((int) PixelUtil.toPixelFromDIP(x), (int) PixelUtil.toPixelFromDIP(y));
309+
}
302310
}

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

+31-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public class ReactScrollView extends ScrollView
5555
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
5656
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
5757

58+
private static final int UNSET_CONTENT_OFFSET = -1;
59+
5860
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
5961
private final @Nullable OverScroller mScroller;
6062
private final VelocityHelper mVelocityHelper = new VelocityHelper();
@@ -81,6 +83,8 @@ public class ReactScrollView extends ScrollView
8183
private boolean mSnapToEnd = true;
8284
private View mContentView;
8385
private ReactViewBackgroundManager mReactBackgroundManager;
86+
private int pendingContentOffsetX = UNSET_CONTENT_OFFSET;
87+
private int pendingContentOffsetY = UNSET_CONTENT_OFFSET;
8488
private @Nullable StateWrapper mStateWrapper;
8589

8690
public ReactScrollView(ReactContext context) {
@@ -200,7 +204,13 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
200204
@Override
201205
protected void onLayout(boolean changed, int l, int t, int r, int b) {
202206
// Call with the present values in order to re-layout if necessary
203-
reactScrollTo(getScrollX(), getScrollY());
207+
// If a "pending" value has been set, we restore that value.
208+
// That value gets cleared by reactScrollTo.
209+
int scrollToX =
210+
pendingContentOffsetX != UNSET_CONTENT_OFFSET ? pendingContentOffsetX : getScrollX();
211+
int scrollToY =
212+
pendingContentOffsetY != UNSET_CONTENT_OFFSET ? pendingContentOffsetY : getScrollY();
213+
reactScrollTo(scrollToX, scrollToY);
204214
}
205215

206216
@Override
@@ -777,6 +787,7 @@ public void onChildViewRemoved(View parent, View child) {
777787
public void reactSmoothScrollTo(int x, int y) {
778788
smoothScrollTo(x, y);
779789
updateStateOnScroll(x, y);
790+
setPendingContentOffsets(x, y);
780791
}
781792

782793
/**
@@ -788,6 +799,25 @@ public void reactSmoothScrollTo(int x, int y) {
788799
public void reactScrollTo(int x, int y) {
789800
scrollTo(x, y);
790801
updateStateOnScroll(x, y);
802+
setPendingContentOffsets(x, y);
803+
}
804+
805+
/**
806+
* If contentOffset is set before the View has been laid out, store the values and set them when
807+
* `onLayout` is called.
808+
*
809+
* @param x
810+
* @param y
811+
*/
812+
private void setPendingContentOffsets(int x, int y) {
813+
View child = getChildAt(0);
814+
if (child != null && child.getWidth() != 0 && child.getHeight() != 0) {
815+
pendingContentOffsetX = UNSET_CONTENT_OFFSET;
816+
pendingContentOffsetY = UNSET_CONTENT_OFFSET;
817+
} else {
818+
pendingContentOffsetX = x;
819+
pendingContentOffsetY = y;
820+
}
791821
}
792822

793823
/**

0 commit comments

Comments
 (0)