Skip to content

Implement OnScrollEndDrag Event Handler for ScrollView #14473

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

Merged
merged 14 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Implement onScrollEndDrag prop",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
3 changes: 3 additions & 0 deletions packages/playground/Samples/scrollViewSnapSample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ export default class Bootstrap extends React.Component<{}, any> {
onScrollBeginDrag={() => {
console.log('onScrollBeginDrag');
}}
onScrollEndDrag={() => {
console.log('onScrollEndDrag');
}}
onScroll={() => {
console.log('onScroll');
}}>
Expand Down
1 change: 1 addition & 0 deletions vnext/Microsoft.ReactNative/CompositionSwitcher.idl
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ namespace Microsoft.ReactNative.Composition.Experimental
void ScrollEnabled(Boolean isScrollEnabled);
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollBeginDrag;
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollEndDrag;
void ContentSize(Windows.Foundation.Numerics.Vector2 size);
Windows.Foundation.Numerics.Vector3 ScrollPosition { get; };
void ScrollBy(Windows.Foundation.Numerics.Vector3 offset, Boolean animate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,11 +709,18 @@ struct CompScrollerVisual : winrt::implements<
m_outer->m_custom = false;
m_outer->m_inertia = true;
m_outer->m_currentPosition = args.NaturalRestingPosition();
m_outer->FireScrollBeginDrag({args.NaturalRestingPosition().x, args.NaturalRestingPosition().y});
// When the user stops interacting with the object, tracker can go into two paths:
// 1. tracker goes into idle state immediately
// 2. tracker has just started gliding into Inertia state
// Fire ScrollEndDrag
m_outer->FireScrollEndDrag({args.NaturalRestingPosition().x, args.NaturalRestingPosition().y});
}
void InteractingStateEntered(
typename TTypeRedirects::InteractionTracker sender,
typename TTypeRedirects::InteractionTrackerInteractingStateEnteredArgs args) noexcept {}
typename TTypeRedirects::InteractionTrackerInteractingStateEnteredArgs args) noexcept {
// Fire when the user starts dragging the object
m_outer->FireScrollBeginDrag({sender.Position().x, sender.Position().y});
}
void RequestIgnored(
typename TTypeRedirects::InteractionTracker sender,
typename TTypeRedirects::InteractionTrackerRequestIgnoredArgs args) noexcept {}
Expand Down Expand Up @@ -927,6 +934,13 @@ struct CompScrollerVisual : winrt::implements<
return m_scrollBeginDragEvent.add(handler);
}

winrt::event_token ScrollEndDrag(
winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs> const
&handler) noexcept {
return m_scrollEndDragEvent.add(handler);
}

void ScrollPositionChanged(winrt::event_token const &token) noexcept {
m_scrollPositionChangedEvent.remove(token);
}
Expand All @@ -935,6 +949,10 @@ struct CompScrollerVisual : winrt::implements<
m_scrollBeginDragEvent.remove(token);
}

void ScrollEndDrag(winrt::event_token const &token) noexcept {
m_scrollEndDragEvent.remove(token);
}

void ContentSize(winrt::Windows::Foundation::Numerics::float2 const &size) noexcept {
m_contentSize = size;
m_contentVisual.Size(size);
Expand Down Expand Up @@ -1009,6 +1027,10 @@ struct CompScrollerVisual : winrt::implements<
m_scrollBeginDragEvent(*this, winrt::make<CompScrollPositionChangedArgs>(position));
}

void FireScrollEndDrag(winrt::Windows::Foundation::Numerics::float2 position) noexcept {
m_scrollEndDragEvent(*this, winrt::make<CompScrollPositionChangedArgs>(position));
}

void UpdateMaxPosition() noexcept {
m_interactionTracker.MaxPosition(
{std::max<float>(m_contentSize.x - m_visualSize.x, 0),
Expand All @@ -1030,6 +1052,9 @@ struct CompScrollerVisual : winrt::implements<
winrt::event<winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
m_scrollBeginDragEvent;
winrt::event<winrt::Windows::Foundation::EventHandler<
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
m_scrollEndDragEvent;
typename TTypeRedirects::SpriteVisual m_visual{nullptr};
typename TTypeRedirects::SpriteVisual m_contentVisual{nullptr};
typename TTypeRedirects::InteractionTracker m_interactionTracker{nullptr};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,27 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
}
});

m_scrollEndDragRevoker = m_scrollVisual.ScrollEndDrag(
winrt::auto_revoke,
[this](
winrt::IInspectable const & /*sender*/,
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
updateStateWithContentOffset();
auto eventEmitter = GetEventEmitter();
if (eventEmitter) {
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
scrollMetrics.zoomScale = 1.0;
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
->onScrollEndDrag(scrollMetrics);
}
});

return visual;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollBeginDrag_revoker
m_scrollBeginDragRevoker{};

winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollEndDrag_revoker
m_scrollEndDragRevoker{};

float m_zoomFactor{1.0f};
bool m_isScrollingFromInertia = false;
bool m_isScrolling = false;
Expand Down