mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
introduce experimental mechanism to subscribe to synchronous onScroll (#43593)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43593 changelog: [internal] Introduce experimental mechanism to subscribe to synchronous onScroll from outside of ScrollView. Reviewed By: yungsters Differential Revision: D55066385 fbshipit-source-id: b7cc173cb32e736f49496c1e0ba37de782ca02aa
This commit is contained in:
committed by
Facebook GitHub Bot
parent
274faf4b39
commit
95a8c99f3f
@@ -33,6 +33,7 @@ import StyleSheet from '../../StyleSheet/StyleSheet';
|
||||
import Dimensions from '../../Utilities/Dimensions';
|
||||
import dismissKeyboard from '../../Utilities/dismissKeyboard';
|
||||
import Platform from '../../Utilities/Platform';
|
||||
import EventEmitter from '../../vendor/emitter/EventEmitter';
|
||||
import Keyboard from '../Keyboard/Keyboard';
|
||||
import TextInputState from '../TextInput/TextInputState';
|
||||
import View from '../View/View';
|
||||
@@ -676,6 +677,9 @@ export type Props = $ReadOnly<{|
|
||||
|
||||
type State = {|
|
||||
layoutHeight: ?number,
|
||||
onScrollEmitter: ?EventEmitter<{
|
||||
scroll: [{x: number, y: number}],
|
||||
}>,
|
||||
|};
|
||||
|
||||
const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16;
|
||||
@@ -761,6 +765,7 @@ class ScrollView extends React.Component<Props, State> {
|
||||
|
||||
state: State = {
|
||||
layoutHeight: null,
|
||||
onScrollEmitter: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@@ -829,6 +834,8 @@ class ScrollView extends React.Component<Props, State> {
|
||||
if (this._scrollAnimatedValueAttachment) {
|
||||
this._scrollAnimatedValueAttachment.detach();
|
||||
}
|
||||
|
||||
this.state.onScrollEmitter?.removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -948,6 +955,40 @@ class ScrollView extends React.Component<Props, State> {
|
||||
Commands.flashScrollIndicators(this._scrollView.nativeInstance);
|
||||
};
|
||||
|
||||
_subscribeToOnScroll: (
|
||||
callback: ({x: number, y: number}) => void,
|
||||
) => EventSubscription = callback => {
|
||||
// An undefined value means the listener has not been added, yet.
|
||||
// A null value means the listener has been removed.
|
||||
let subscription: ?EventSubscription;
|
||||
|
||||
this.setState(
|
||||
({onScrollEmitter}) => ({
|
||||
onScrollEmitter: onScrollEmitter ?? new EventEmitter(),
|
||||
}),
|
||||
() => {
|
||||
// If `subscription` is null, that means it was removed before we got
|
||||
// here so do nothing.
|
||||
if (subscription !== null) {
|
||||
subscription = nullthrows(this.state.onScrollEmitter).addListener(
|
||||
'scroll',
|
||||
callback,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
remove() {
|
||||
// If `subscription` was created before this invocation, remove it.
|
||||
subscription?.remove();
|
||||
// Record this invocation by setting `subscription` to null, in case it
|
||||
// ends up being created after this invocation.
|
||||
subscription = null;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This method should be used as the callback to onFocus in a TextInputs'
|
||||
* parent view. Note that any module using this mixin needs to return
|
||||
@@ -1154,6 +1195,11 @@ class ScrollView extends React.Component<Props, State> {
|
||||
_handleScroll = (e: ScrollEvent) => {
|
||||
this._observedScrollSinceBecomingResponder = true;
|
||||
this.props.onScroll && this.props.onScroll(e);
|
||||
|
||||
this.state.onScrollEmitter?.emit('scroll', {
|
||||
x: e.nativeEvent.contentOffset.x,
|
||||
y: e.nativeEvent.contentOffset.y,
|
||||
});
|
||||
};
|
||||
|
||||
_handleLayout = (e: LayoutEvent) => {
|
||||
@@ -1202,6 +1248,8 @@ class ScrollView extends React.Component<Props, State> {
|
||||
scrollToEnd: this.scrollToEnd,
|
||||
flashScrollIndicators: this.flashScrollIndicators,
|
||||
scrollResponderZoomTo: this.scrollResponderZoomTo,
|
||||
// TODO: Replace unstable_subscribeToOnScroll once scrollView.addEventListener('scroll', (e: ScrollEvent) => {}, {passive: false});
|
||||
unstable_subscribeToOnScroll: this._subscribeToOnScroll,
|
||||
scrollResponderScrollNativeHandleToKeyboard:
|
||||
this.scrollResponderScrollNativeHandleToKeyboard,
|
||||
},
|
||||
@@ -1778,6 +1826,7 @@ class ScrollView extends React.Component<Props, State> {
|
||||
onScroll: this._handleScroll,
|
||||
endDraggingSensitivityMultiplier:
|
||||
experimental_endDraggingSensitivityMultiplier,
|
||||
enableSyncOnScroll: this.state.onScrollEmitter ? true : undefined,
|
||||
scrollEventThrottle: hasStickyHeaders
|
||||
? 1
|
||||
: this.props.scrollEventThrottle,
|
||||
|
||||
+1
@@ -133,6 +133,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
|
||||
contentInsetAdjustmentBehavior: true,
|
||||
decelerationRate: true,
|
||||
endDraggingSensitivityMultiplier: true,
|
||||
enableSyncOnScroll: true, // iOS-Fabric only.
|
||||
directionalLockEnabled: true,
|
||||
disableIntervalMomentum: true,
|
||||
indicatorStyle: true,
|
||||
|
||||
+23
-8
@@ -459,18 +459,33 @@ static void RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrol
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
if (!_isUserTriggeredScrolling || CoreFeatures::enableGranularScrollViewStateUpdatesIOS) {
|
||||
[self _updateStateWithContentOffset];
|
||||
}
|
||||
const auto &props = static_cast<const ScrollViewProps &>(*_props);
|
||||
auto scrollMetrics = [self _scrollViewMetrics];
|
||||
|
||||
NSTimeInterval now = CACurrentMediaTime();
|
||||
if ((_lastScrollEventDispatchTime == 0) || (now - _lastScrollEventDispatchTime > _scrollEventThrottle)) {
|
||||
_lastScrollEventDispatchTime = now;
|
||||
if (props.enableSyncOnScroll) {
|
||||
if (_eventEmitter) {
|
||||
static_cast<const ScrollViewEventEmitter &>(*_eventEmitter).onScroll([self _scrollViewMetrics]);
|
||||
const auto &eventEmitter = static_cast<const ScrollViewEventEmitter &>(*_eventEmitter);
|
||||
// TODO: temporary API to unblock testing of synchronous rendering.
|
||||
eventEmitter.experimental_flushSync([&eventEmitter, &scrollMetrics, &self]() {
|
||||
[self _updateStateWithContentOffset];
|
||||
// TODO: temporary API to unblock testing of synchronous rendering.
|
||||
eventEmitter.experimental_onDiscreteScroll(scrollMetrics);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!_isUserTriggeredScrolling || CoreFeatures::enableGranularScrollViewStateUpdatesIOS) {
|
||||
[self _updateStateWithContentOffset];
|
||||
}
|
||||
|
||||
RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag);
|
||||
NSTimeInterval now = CACurrentMediaTime();
|
||||
if ((_lastScrollEventDispatchTime == 0) || (now - _lastScrollEventDispatchTime > _scrollEventThrottle)) {
|
||||
_lastScrollEventDispatchTime = now;
|
||||
if (_eventEmitter) {
|
||||
static_cast<const ScrollViewEventEmitter &>(*_eventEmitter).onScroll(scrollMetrics);
|
||||
}
|
||||
|
||||
RCTSendScrollEventForNativeAnimations_DEPRECATED(scrollView, self.tag);
|
||||
}
|
||||
}
|
||||
|
||||
[self _remountChildrenIfNeeded];
|
||||
|
||||
+10
@@ -63,6 +63,16 @@ void ScrollViewEventEmitter::onScroll(const Metrics& scrollViewMetrics) const {
|
||||
});
|
||||
}
|
||||
|
||||
void ScrollViewEventEmitter::experimental_onDiscreteScroll(
|
||||
const Metrics& scrollViewMetrics) const {
|
||||
dispatchEvent(
|
||||
"scroll",
|
||||
[scrollViewMetrics](jsi::Runtime& runtime) {
|
||||
return scrollViewMetricsPayload(runtime, scrollViewMetrics);
|
||||
},
|
||||
RawEvent::Category::Discrete);
|
||||
}
|
||||
|
||||
void ScrollViewEventEmitter::onScrollToTop(
|
||||
const Metrics& scrollViewMetrics) const {
|
||||
dispatchUniqueEvent(
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ class ScrollViewEventEmitter : public ViewEventEmitter {
|
||||
};
|
||||
|
||||
void onScroll(const Metrics& scrollViewMetrics) const;
|
||||
void experimental_onDiscreteScroll(const Metrics& scrollViewMetrics) const;
|
||||
void onScrollBeginDrag(const Metrics& scrollViewMetrics) const;
|
||||
void onScrollEndDrag(const Metrics& scrollViewMetrics) const;
|
||||
void onMomentumScrollBegin(const Metrics& scrollViewMetrics) const;
|
||||
|
||||
+11
@@ -109,6 +109,15 @@ ScrollViewProps::ScrollViewProps(
|
||||
"endDraggingSensitivityMultiplier",
|
||||
sourceProps.endDraggingSensitivityMultiplier,
|
||||
1)),
|
||||
enableSyncOnScroll(
|
||||
CoreFeatures::enablePropIteratorSetter
|
||||
? sourceProps.enableSyncOnScroll
|
||||
: convertRawProp(
|
||||
context,
|
||||
rawProps,
|
||||
"enableSyncOnScroll",
|
||||
sourceProps.enableSyncOnScroll,
|
||||
false)),
|
||||
directionalLockEnabled(
|
||||
CoreFeatures::enablePropIteratorSetter
|
||||
? sourceProps.directionalLockEnabled
|
||||
@@ -368,6 +377,8 @@ void ScrollViewProps::setProp(
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(maximumZoomScale);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(minimumZoomScale);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollEnabled);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(enableSyncOnScroll);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(endDraggingSensitivityMultiplier);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(pagingEnabled);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(pinchGestureEnabled);
|
||||
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollsToTop);
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ class ScrollViewProps final : public ViewProps {
|
||||
bool automaticallyAdjustsScrollIndicatorInsets{true};
|
||||
Float decelerationRate{0.998f};
|
||||
Float endDraggingSensitivityMultiplier{1};
|
||||
bool enableSyncOnScroll{false};
|
||||
bool directionalLockEnabled{};
|
||||
ScrollViewIndicatorStyle indicatorStyle{};
|
||||
ScrollViewKeyboardDismissMode keyboardDismissMode{};
|
||||
|
||||
@@ -583,7 +583,7 @@ jsi::Value UIManagerBinding::get(
|
||||
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
|
||||
*shadowNodeFromValue(runtime, arguments[0]),
|
||||
shadowNodeFromValue(runtime, arguments[1]).get(),
|
||||
{/* .includeTransform = */ true});
|
||||
{/* .includeTransform = */ false});
|
||||
auto frame = layoutMetrics.frame;
|
||||
auto result = jsi::Object(runtime);
|
||||
result.setProperty(runtime, "left", frame.origin.x);
|
||||
|
||||
Reference in New Issue
Block a user