Skip to content

Commit 0ef5bee

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Support ScrollAway in ReactScrollView
Summary: Support ScrollAway in ReactScrollView for Fabric/non-Fabric. Changelog: [Android][Added] Support for ScrollAway native nav bars added to ReactScrollView Reviewed By: mdvacca Differential Revision: D28308855 fbshipit-source-id: 9a922159ef50fb7c8e9c484a4b97ca57ab248496
1 parent d87542e commit 0ef5bee

File tree

6 files changed

+78
-12
lines changed

6 files changed

+78
-12
lines changed

ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactClippingViewGroupHelper.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ public static void calculateClippingRect(View view, Rect outputRect) {
4343
// Intersect the view with the parent's rectangle
4444
// This will result in the overlap with coordinates in the parent space
4545
if (!sHelperRect.intersect(
46-
view.getLeft(), view.getTop(), view.getRight(), view.getBottom())) {
46+
view.getLeft(),
47+
view.getTop() + (int) view.getTranslationY(),
48+
view.getRight(),
49+
view.getBottom() + (int) view.getTranslationY())) {
4750
outputRect.setEmpty();
4851
return;
4952
}
5053
// Now we move the coordinates to the View's coordinate space
5154
sHelperRect.offset(-view.getLeft(), -view.getTop());
55+
sHelperRect.offset(-(int) view.getTranslationX(), -(int) view.getTranslationY());
5256
sHelperRect.offset(view.getScrollX(), view.getScrollY());
5357
outputRect.set(sHelperRect);
5458
return;

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

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
6060
private static boolean sTriedToGetScrollerField = false;
6161
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
6262
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
63+
private static final String SCROLL_AWAY_PADDING_TOP = "scrollAwayPaddingTop";
6364
private int mLayoutDirection;
6465

6566
private int mScrollXAfterMeasure = NO_SCROLL_POSITION;
@@ -1214,6 +1215,7 @@ public WritableMap getStateUpdate() {
12141215
WritableMap map = new WritableNativeMap();
12151216
map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(fabricScrollX));
12161217
map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(scrollY));
1218+
map.putDouble(SCROLL_AWAY_PADDING_TOP, 0);
12171219
return map;
12181220
}
12191221
});

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

+61-5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class ReactScrollView extends ScrollView
5858
private static boolean sTriedToGetScrollerField = false;
5959
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
6060
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
61+
private static final String SCROLL_AWAY_PADDING_TOP = "scrollAwayPaddingTop";
6162

6263
private static final int UNSET_CONTENT_OFFSET = -1;
6364

@@ -95,6 +96,8 @@ public class ReactScrollView extends ScrollView
9596
private int mFinalAnimatedPositionScrollX;
9697
private int mFinalAnimatedPositionScrollY;
9798

99+
private int mScrollAwayPaddingTop = 0;
100+
98101
private int mLastStateUpdateScrollX = -1;
99102
private int mLastStateUpdateScrollY = -1;
100103

@@ -959,6 +962,41 @@ public void setBorderStyle(@Nullable String style) {
959962
mReactBackgroundManager.setBorderStyle(style);
960963
}
961964

965+
/**
966+
* ScrollAway: This enables a natively-controlled navbar that optionally obscures the top content
967+
* of the ScrollView. Whether or not the navbar is obscuring the React Native surface is
968+
* determined outside of React Native.
969+
*
970+
* <p>Note: all ScrollViews and HorizontalScrollViews in React have exactly one child: the
971+
* "content" View (see ScrollView.js). That View is non-collapsable so it will never be
972+
* View-flattened away. However, it is possible to pass custom styles into that View.
973+
*
974+
* <p>If you are using this feature it is assumed that you have full control over this ScrollView
975+
* and that you are **not** overriding the ScrollView content view to pass in a `translateY`
976+
* style. `translateY` must never be set from ReactJS while using this feature!
977+
*/
978+
public void setScrollAwayTopPaddingEnabledUnstable(int topPadding) {
979+
int count = getChildCount();
980+
981+
Assertions.assertCondition(
982+
count == 1, "React Native ScrollView always has exactly 1 child; a content View");
983+
984+
if (count > 0) {
985+
for (int i = 0; i < count; i++) {
986+
View childView = getChildAt(i);
987+
childView.setTranslationY(topPadding);
988+
}
989+
990+
// Add the topPadding value as the bottom padding for the ScrollView.
991+
// Otherwise, we'll push down the contents of the scroll view down too
992+
// far off screen.
993+
setPadding(0, 0, 0, topPadding);
994+
}
995+
996+
updateScrollAwayState(topPadding);
997+
setRemoveClippedSubviews(mRemoveClippedSubviews);
998+
}
999+
9621000
/**
9631001
* Called on any stabilized onScroll change to propagate content offset value to a Shadow Node.
9641002
*/
@@ -971,23 +1009,41 @@ private void updateStateOnScroll(final int scrollX, final int scrollY) {
9711009
mLastStateUpdateScrollX = scrollX;
9721010
mLastStateUpdateScrollY = scrollY;
9731011

1012+
forceUpdateState();
1013+
}
1014+
1015+
private void updateStateOnScroll() {
1016+
updateStateOnScroll(getScrollX(), getScrollY());
1017+
}
1018+
1019+
private void updateScrollAwayState(int scrollAwayPaddingTop) {
1020+
if (mScrollAwayPaddingTop == scrollAwayPaddingTop) {
1021+
return;
1022+
}
1023+
1024+
mScrollAwayPaddingTop = scrollAwayPaddingTop;
1025+
1026+
forceUpdateState();
1027+
}
1028+
1029+
private void forceUpdateState() {
1030+
final int scrollX = mLastStateUpdateScrollX;
1031+
final int scrollY = mLastStateUpdateScrollY;
1032+
final int scrollAwayPaddingTop = mScrollAwayPaddingTop;
1033+
9741034
mFabricViewStateManager.setState(
9751035
new FabricViewStateManager.StateUpdateCallback() {
9761036
@Override
9771037
public WritableMap getStateUpdate() {
978-
9791038
WritableMap map = new WritableNativeMap();
9801039
map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(scrollX));
9811040
map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(scrollY));
1041+
map.putDouble(SCROLL_AWAY_PADDING_TOP, PixelUtil.toDIPFromPixel(scrollAwayPaddingTop));
9821042
return map;
9831043
}
9841044
});
9851045
}
9861046

987-
private void updateStateOnScroll() {
988-
updateStateOnScroll(getScrollX(), getScrollY());
989-
}
990-
9911047
@Override
9921048
public FabricViewStateManager getFabricViewStateManager() {
9931049
return mFabricViewStateManager;

ReactCommon/react/renderer/components/scrollview/ScrollViewShadowNode.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void ScrollViewShadowNode::updateStateIfNeeded() {
3434
void ScrollViewShadowNode::updateScrollContentOffsetIfNeeded() {
3535
#ifndef ANDROID
3636
if (getLayoutMetrics().layoutDirection == LayoutDirection::RightToLeft) {
37-
// Yoga place `contentView` on the right side of `scrollView` when RTL
37+
// Yoga places `contentView` on the right side of `scrollView` when RTL
3838
// layout is enforced. To correct for this, in RTL setting, correct the
3939
// frame's origin. React Native Classic does this as well in
4040
// `RCTScrollContentShadowView.m`.
@@ -58,8 +58,9 @@ void ScrollViewShadowNode::layout(LayoutContext layoutContext) {
5858
}
5959

6060
Point ScrollViewShadowNode::getContentOriginOffset() const {
61-
auto contentOffset = getStateData().contentOffset;
62-
return {-contentOffset.x, -contentOffset.y};
61+
auto stateData = getStateData();
62+
auto contentOffset = stateData.contentOffset;
63+
return {-contentOffset.x, -contentOffset.y + stateData.scrollAwayPaddingTop};
6364
}
6465

6566
} // namespace react

ReactCommon/react/renderer/components/scrollview/ScrollViewState.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ScrollViewState final {
2525
public:
2626
Point contentOffset;
2727
Rect contentBoundingRect;
28+
int scrollAwayPaddingTop;
2829

2930
/*
3031
* Returns size of scrollable area.
@@ -37,11 +38,13 @@ class ScrollViewState final {
3738
: contentOffset(
3839
{(Float)data["contentOffsetLeft"].getDouble(),
3940
(Float)data["contentOffsetTop"].getDouble()}),
40-
contentBoundingRect({}){};
41+
contentBoundingRect({}),
42+
scrollAwayPaddingTop((Float)data["scrollAwayPaddingTop"].getDouble()){};
4143

4244
folly::dynamic getDynamic() const {
4345
return folly::dynamic::object("contentOffsetLeft", contentOffset.x)(
44-
"contentOffsetTop", contentOffset.y);
46+
"contentOffsetTop", contentOffset.y)(
47+
"scrollAwayPaddingTop", scrollAwayPaddingTop);
4548
};
4649
MapBuffer getMapBuffer() const {
4750
return MapBufferBuilder::EMPTY();

ReactCommon/react/renderer/core/tests/ConcreteShadowNodeTest.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ TEST(ConcreteShadowNodeTest, testSetStateData) {
2626

2727
auto shadowNode = builder.build(element);
2828

29-
shadowNode->setStateData({{10, 11}, {{21, 22}, {301, 302}}});
29+
shadowNode->setStateData({{10, 11}, {{21, 22}, {301, 302}}, 0});
3030

3131
EXPECT_NE(
3232
shadowNode->getState(), shadowNode->getFamily().getMostRecentState());

0 commit comments

Comments
 (0)