Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] [Fix]:-Unify onMomentumEnd callback behaviour in android with iOS #43654

Closed
wants to merge 3 commits into from
Closed
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 @@ -90,6 +90,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private boolean mDragging;
private boolean mPagingEnabled = false;
private @Nullable Runnable mPostTouchRunnable;
private @Nullable Runnable mPostSmoothScrollRunnable;
private boolean mRemoveClippedSubviews;
private boolean mScrollEnabled = true;
private boolean mSendMomentumEvents;
Expand Down Expand Up @@ -888,6 +889,7 @@ private void handlePostTouchScrolling(int velocityX, int velocityY) {
}

if (mSendMomentumEvents) {
enableFpsListener();
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this, velocityX, velocityY);
}

Expand Down Expand Up @@ -923,8 +925,8 @@ public void run() {
mPostTouchRunnable = null;
if (mSendMomentumEvents) {
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
disableFpsListener();
}
disableFpsListener();
} else {
if (mPagingEnabled && !mSnappingToPage) {
// If we have pagingEnabled and we have not snapped to the page
Expand All @@ -943,6 +945,42 @@ public void run() {
this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);
}

/**
* This handles any sort of animated scrolling that may occur as a result of an API call on ScrollView.
* For example, calling scrollTo with animated = true, initiates a scroll effect that runs over time.
* To match iOS, and to have a complete API, a onMomentumScrollEnd event should accompany any scroll call
* so that actions can be taken when a scroll is complete. This code maps roughly to handlePostTouchScrolling,
* but is much simpler as it results from a simple API call vs. a user interaction. It only executes if
* momentum events are turned on.
*/
public void handleSmoothScrollMomentumEvents() {
if (!mSendMomentumEvents || null != mPostSmoothScrollRunnable) {
return;
}

enableFpsListener();
mActivelyScrolling = false;
mPostSmoothScrollRunnable = new Runnable() {

@Override
public void run() {
if (mActivelyScrolling) {
// We are still scrolling so we just post to check again a frame later
mActivelyScrolling = false;
ViewCompat.postOnAnimationDelayed(
ReactHorizontalScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY);
} else {
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
ReactHorizontalScrollView.this.mPostSmoothScrollRunnable = null;
disableFpsListener();
}
}
};
ViewCompat.postOnAnimationDelayed(
ReactHorizontalScrollView.this, mPostSmoothScrollRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);

}

private void cancelPostTouchScrolling() {
if (mPostTouchRunnable != null) {
removeCallbacks(mPostTouchRunnable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,10 @@ public void scrollTo(
scrollView.abortAnimation();
if (data.mAnimated) {
scrollView.reactSmoothScrollTo(data.mDestX, data.mDestY);
scrollView.handleSmoothScrollMomentumEvents();
} else {
scrollView.scrollTo(data.mDestX, data.mDestY);
ReactScrollViewHelper.emitScrollMomentumEndEvent(scrollView);
}
}

Expand All @@ -223,8 +225,10 @@ public void scrollToEnd(
scrollView.abortAnimation();
if (data.mAnimated) {
scrollView.reactSmoothScrollTo(right, scrollView.getScrollY());
scrollView.handleSmoothScrollMomentumEvents();
} else {
scrollView.scrollTo(right, scrollView.getScrollY());
ReactScrollViewHelper.emitScrollMomentumEndEvent(scrollView);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public class ReactScrollView extends ScrollView
private boolean mDragging;
private boolean mPagingEnabled = false;
private @Nullable Runnable mPostTouchRunnable;
private @Nullable Runnable mPostSmoothScrollRunnable;
private boolean mRemoveClippedSubviews;
private boolean mScrollEnabled = true;
private boolean mSendMomentumEvents;
Expand Down Expand Up @@ -686,8 +687,8 @@ public void run() {
mPostTouchRunnable = null;
if (mSendMomentumEvents) {
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
disableFpsListener();
}
disableFpsListener();
} else {
if (mPagingEnabled && !mSnappingToPage) {
// If we have pagingEnabled and we have not snapped to the page
Expand All @@ -706,6 +707,43 @@ public void run() {
this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);
}

/**
* This handles any sort of animated scrolling that may occur as a result of an API call on ScrollView.
* For example, calling scrollTo with animated = true, initiates a scroll effect that runs over time.
* To match iOS, and to have a complete API, a onMomentumScrollEnd event should accompany any scroll call
* so that actions can be taken when a scroll is complete. This code maps roughly to handlePostTouchScrolling,
* but is much simpler as it results from a simple API call vs. a user interaction. It only executes if
* momentum events are turned on.
*/
public void handleSmoothScrollMomentumEvents() {
if (!mSendMomentumEvents || null != mPostSmoothScrollRunnable) {
return;
}

enableFpsListener();
mActivelyScrolling = false;
mPostSmoothScrollRunnable = new Runnable() {

@Override
public void run() {
if (mActivelyScrolling) {
// We are still scrolling so we just post to check again a frame later
mActivelyScrolling = false;
ViewCompat.postOnAnimationDelayed(
ReactScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY);
} else {
// There has not been a scroll update since the last time this Runnable executed.
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
ReactScrollView.this.mPostSmoothScrollRunnable = null;
disableFpsListener();
}
}
};
ViewCompat.postOnAnimationDelayed(
ReactScrollView.this, mPostSmoothScrollRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);

}

private void cancelPostTouchScrolling() {
if (mPostTouchRunnable != null) {
removeCallbacks(mPostTouchRunnable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ public void scrollTo(
scrollView.abortAnimation();
if (data.mAnimated) {
scrollView.reactSmoothScrollTo(data.mDestX, data.mDestY);
scrollView.handleSmoothScrollMomentumEvents();
} else {
scrollView.scrollTo(data.mDestX, data.mDestY);
ReactScrollViewHelper.emitScrollMomentumEndEvent(scrollView);
}
}

Expand Down Expand Up @@ -299,8 +301,10 @@ public void scrollToEnd(
scrollView.abortAnimation();
if (data.mAnimated) {
scrollView.reactSmoothScrollTo(scrollView.getScrollX(), bottom);
scrollView.handleSmoothScrollMomentumEvents();
} else {
scrollView.scrollTo(scrollView.getScrollX(), bottom);
ReactScrollViewHelper.emitScrollMomentumEndEvent(scrollView);
}
}

Expand Down
14 changes: 14 additions & 0 deletions packages/rn-tester/js/examples/FlatList/FlatList-basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ class FlatListExample extends React.PureComponent<Props, State> {
this._listRef?.scrollToIndex({viewPosition: 0.5, index: Number(text)});
};

_onChangeScrollOffset = (text: mixed) => {
this._listRef?.scrollToOffset({offset: Number(text), animated: false});
};

// $FlowFixMe[missing-local-annot]
_scrollPos = new Animated.Value(0);
// $FlowFixMe[missing-local-annot]
Expand Down Expand Up @@ -166,6 +170,10 @@ class FlatListExample extends React.PureComponent<Props, State> {
onChangeText={this._onChangeScrollToIndex}
placeholder="scrollToIndex..."
/>
<PlainInput
onChangeText={this._onChangeScrollOffset}
placeholder="scrollToOffset..."
/>
</View>
<View style={styles.options}>
{renderSmallSwitchOption(
Expand Down Expand Up @@ -282,6 +290,12 @@ class FlatListExample extends React.PureComponent<Props, State> {
onScroll={
this.state.horizontal ? this._scrollSinkX : this._scrollSinkY
}
onMomentumScrollEnd={() => {
console.log('onMomentumScrollEnd');
}}
onMomentumScrollBegin={e => {
console.log('onMomentumScrollBegin', e.nativeEvent);
}}
onScrollToIndexFailed={this._onScrollToIndexFailed}
onViewableItemsChanged={this._onViewableItemsChanged}
ref={this._captureRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const {
StyleSheet,
Text,
TouchableOpacity,
View,
Button,
} = require('react-native');

const nullthrows = require('nullthrows');
const NUM_ITEMS = 20;

class ScrollViewSimpleExample extends React.Component<{...}> {
Expand All @@ -37,10 +39,27 @@ class ScrollViewSimpleExample extends React.Component<{...}> {
};

render(): React.Node {
let _scrollView: ?React.ElementRef<typeof ScrollView>;
let _horizontalScrollView1: ?React.ElementRef<typeof ScrollView>;
let _horizontalScrollView2: ?React.ElementRef<typeof ScrollView>;
// One of the items is a horizontal scroll view
const items = this.makeItems(NUM_ITEMS, styles.itemWrapper);
items[4] = (
<ScrollView key={'scrollView'} horizontal={true}>
<ScrollView
ref={scrollView => {
_horizontalScrollView1 = scrollView;
}}
key={'scrollView'}
horizontal={true}
onMomentumScrollEnd={() => {
console.log('First Horizontal ScrollView onMomentumScrollEnd');
}}
onMomentumScrollBegin={e => {
console.log(
'First Horizontal ScrollView onMomentumScrollBegin',
e.nativeEvent,
);
}}>
{this.makeItems(NUM_ITEMS, [
styles.itemWrapper,
styles.horizontalItemWrapper,
Expand All @@ -49,10 +68,22 @@ class ScrollViewSimpleExample extends React.Component<{...}> {
);
items.push(
<ScrollView
ref={scrollView => {
_horizontalScrollView2 = scrollView;
}}
key={'scrollViewSnap'}
horizontal
snapToInterval={210.0}
pagingEnabled>
pagingEnabled
onMomentumScrollEnd={() => {
console.log('Paging Horizontal ScrollView onMomentumScrollEnd');
}}
onMomentumScrollBegin={e => {
console.log(
'Paging Horizontal ScrollView onMomentumScrollBegin',
e.nativeEvent,
);
}}>
{this.makeItems(NUM_ITEMS, [
styles.itemWrapper,
styles.horizontalItemWrapper,
Expand Down Expand Up @@ -100,17 +131,71 @@ class ScrollViewSimpleExample extends React.Component<{...}> {
</ScrollView>,
);

const verticalScrollView = (
<ScrollView style={styles.verticalScrollView}>{items}</ScrollView>
return (
<View style={styles.container}>
<View style={styles.options}>
<Button
title="Animated Scroll to top"
onPress={() => {
nullthrows(_scrollView).scrollTo({x: 0, y: 0, animated: true});
nullthrows(_horizontalScrollView1).scrollTo({
x: 0,
y: 0,
animated: true,
});
nullthrows(_horizontalScrollView2).scrollTo({
x: 0,
y: 0,
animated: true,
});
}}
/>
<Button
title="Animated Scroll to End"
onPress={() => {
nullthrows(_scrollView).scrollToEnd({animated: true});
nullthrows(_horizontalScrollView1).scrollToEnd({animated: true});
nullthrows(_horizontalScrollView2).scrollToEnd({animated: true});
}}
color={'blue'}
/>
</View>
<ScrollView
ref={scrollView => {
_scrollView = scrollView;
}}
style={styles.verticalScrollView}
onMomentumScrollEnd={() => {
console.log('Vertical ScrollView onMomentumScrollEnd');
}}
onMomentumScrollBegin={e => {
console.log(
'Vertical ScrollView onMomentumScrollBegin',
e.nativeEvent,
);
}}>
{items}
</ScrollView>
</View>
);

return verticalScrollView;
}
}

const styles = StyleSheet.create({
container: {
backgroundColor: 'rgb(239, 239, 244)',
flex: 1,
},
verticalScrollView: {
margin: 10,
backgroundColor: 'white',
flexGrow: 1,
},
options: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'center',
},
itemWrapper: {
backgroundColor: '#dddddd',
Expand Down
Loading