Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,15 @@ export interface ScrollViewPropsAndroid {
* Causes the scrollbars not to turn transparent when they are not in use. The default value is false.
*/
persistentScrollbar?: boolean | undefined;

/**
* When false, the ScrollView will not automatically scroll to a focused child when
* the child requests focus. This can be useful when you want to control the scroll
* position programmatically. The default value is true.
*
* @platform android
*/
scrollsChildToFocus?: boolean | undefined;
}

export interface ScrollViewProps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,14 @@ export type ScrollViewPropsAndroid = Readonly<{
* @platform android
*/
fadingEdgeLength?: ?number | {start: number, end: number},
/**
* When false, the ScrollView will not automatically scroll to a focused child when
* the child requests focus. This can be useful when you want to control the scroll
* position programmatically. The default value is true.
*
* @platform android
*/
scrollsChildToFocus?: ?boolean,
}>;

type StickyHeaderComponentType = component(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
},
pointerEvents: true,
isInvertedVirtualizedList: true,
scrollsChildToFocus: true,
},
}
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type ScrollViewNativeProps = Readonly<{
scrollPerfTag?: ?string,
scrollToOverflowEnabled?: ?boolean,
scrollsToTop?: ?boolean,
scrollsChildToFocus?: ?boolean,
sendMomentumEvents?: ?boolean,
showsHorizontalScrollIndicator?: ?boolean,
showsVerticalScrollIndicator?: ?boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private int mFadingEdgeLengthStart = 0;
private int mFadingEdgeLengthEnd = 0;
private boolean mEmittedOverScrollSinceScrollBegin = false;
private boolean mScrollsChildToFocus = true;

public ReactHorizontalScrollView(Context context) {
this(context, null);
Expand Down Expand Up @@ -199,6 +200,7 @@ private void initView() {
mMaintainVisibleContentPositionHelper = null;
mFadingEdgeLengthStart = 0;
mFadingEdgeLengthEnd = 0;
mScrollsChildToFocus = true;
}

/* package */ void recycleView() {
Expand Down Expand Up @@ -324,6 +326,10 @@ public void setPagingEnabled(boolean pagingEnabled) {
mPagingEnabled = pagingEnabled;
}

public void setScrollsChildToFocus(boolean scrollsChildToFocus) {
mScrollsChildToFocus = scrollsChildToFocus;
}

public void setDecelerationRate(float decelerationRate) {
getReactScrollViewScrollState().setDecelerationRate(decelerationRate);

Expand Down Expand Up @@ -545,7 +551,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
*/
@Override
public void requestChildFocus(View child, View focused) {
if (focused != null && !mPagingEnabled) {
if (focused != null && !mPagingEnabled && mScrollsChildToFocus) {
scrollToChild(focused);
}
requestChildFocusWithoutScroll(child, focused);
Expand All @@ -560,6 +566,14 @@ protected void requestChildFocusWithoutScroll(View child, View focused) {
super.requestChildFocus(child, focused);
}

@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
if (!mScrollsChildToFocus) {
return false;
}
return super.requestChildRectangleOnScreen(child, rectangle, immediate);
}

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
if (mPagingEnabled && !mPagedArrowScrolling) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ constructor(private val fpsListener: FpsListener? = null) :
view.setDisableIntervalMomentum(disableIntervalMomentum)
}

@ReactProp(name = "scrollsChildToFocus", defaultBoolean = true)
public fun setScrollsChildToFocus(view: ReactHorizontalScrollView, scrollsChildToFocus: Boolean) {
view.setScrollsChildToFocus(scrollsChildToFocus)
}

@ReactProp(name = "snapToInterval")
public fun setSnapToInterval(view: ReactHorizontalScrollView, snapToInterval: Float) {
// snapToInterval needs to be exposed as a float because of the Javascript interface.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public class ReactScrollView extends ScrollView
private int mFadingEdgeLengthStart;
private int mFadingEdgeLengthEnd;
private boolean mEmittedOverScrollSinceScrollBegin;
private boolean mScrollsChildToFocus;

public ReactScrollView(Context context) {
this(context, null);
Expand Down Expand Up @@ -196,6 +197,7 @@ private void initView() {
mFadingEdgeLengthStart = 0;
mFadingEdgeLengthEnd = 0;
mEmittedOverScrollSinceScrollBegin = false;
mScrollsChildToFocus = true;
}

/* package */ void recycleView() {
Expand Down Expand Up @@ -298,6 +300,10 @@ public void setPagingEnabled(boolean pagingEnabled) {
mPagingEnabled = pagingEnabled;
}

public void setScrollsChildToFocus(boolean scrollsChildToFocus) {
mScrollsChildToFocus = scrollsChildToFocus;
}

public void setDecelerationRate(float decelerationRate) {
getReactScrollViewScrollState().setDecelerationRate(decelerationRate);

Expand Down Expand Up @@ -507,7 +513,7 @@ protected void onDetachedFromWindow() {
*/
@Override
public void requestChildFocus(View child, View focused) {
if (focused != null) {
if (focused != null && mScrollsChildToFocus) {
scrollToChild(focused);
}
requestChildFocusWithoutScroll(child, focused);
Expand All @@ -522,6 +528,14 @@ protected void requestChildFocusWithoutScroll(View child, View focused) {
super.requestChildFocus(child, focused);
}

@Override
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
if (!mScrollsChildToFocus) {
return false;
}
return super.requestChildRectangleOnScreen(child, rectangle, immediate);
}

private int getScrollDelta(View descendent) {
descendent.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(descendent, mTempRect);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ constructor(private val fpsListener: FpsListener? = null) :
view.setDisableIntervalMomentum(disableIntervalMomentum)
}

@ReactProp(name = "scrollsChildToFocus", defaultBoolean = true)
public fun setScrollsChildToFocus(view: ReactScrollView, scrollsChildToFocus: Boolean) {
view.setScrollsChildToFocus(scrollsChildToFocus)
}

@ReactProp(name = "snapToInterval")
public fun setSnapToInterval(view: ReactScrollView, snapToInterval: Float) {
// snapToInterval needs to be exposed as a float because of the Javascript interface.
Expand Down
47 changes: 47 additions & 0 deletions packages/rn-tester/js/examples/ScrollView/ScrollViewExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,14 @@ if (Platform.OS === 'ios') {
return <AndroidScrollBarOptions />;
},
});
examples.push({
title: '<ScrollView> scrollsChildToFocus\n',
description:
'When false, the ScrollView will not automatically scroll to a focused child. Useful for controlling scroll position programmatically.',
render(): React.Node {
return <ScrollsChildToFocusExample />;
},
});
}
exports.examples = examples;

Expand All @@ -566,6 +574,45 @@ const AndroidScrollBarOptions = () => {
);
};

const ScrollsChildToFocusExample = () => {
const [scrollsChildToFocus, setScrollsChildToFocus] = useState(true);
return (
<View>
<RNTesterText style={styles.text}>
Focus a TextInput below to see the scroll behavior.
</RNTesterText>
<ScrollView
style={[styles.scrollView, {height: 200}]}
nestedScrollEnabled
scrollsChildToFocus={scrollsChildToFocus}>
<View style={{padding: 10}}>
<TextInput
style={styles.textInput}
placeholder="TextInput 1 (top)"
/>
<View style={{height: 100}} />
<TextInput style={styles.textInput} placeholder="TextInput 2" />
<View style={{height: 100}} />
<TextInput style={styles.textInput} placeholder="TextInput 3" />
<View style={{height: 100}} />
<TextInput
style={styles.textInput}
placeholder="TextInput 4 (bottom)"
/>
</View>
</ScrollView>
<Button
label={'scrollsChildToFocus: ' + scrollsChildToFocus.toString()}
onPress={() => setScrollsChildToFocus(!scrollsChildToFocus)}
/>
<RNTesterText>
When false, focusing a TextInput will not automatically scroll it into
view.
</RNTesterText>
</View>
);
};

const HorizontalScrollView = (props: {
direction: 'ltr' | 'rtl',
itemCount?: number,
Expand Down
Loading