Skip to content

Commit f640c8e

Browse files
authored
Implement OnScrollEndDrag Event Handler for ScrollView (#14473)
* Implement onScrollEndDrag prop * Change files * lint fix * Address comments * add noexcept * Address feedback
1 parent 95d1c4d commit f640c8e

File tree

6 files changed

+77
-18
lines changed

6 files changed

+77
-18
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Implement onScrollEndDrag prop",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/playground/Samples/scrollViewSnapSample.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ export default class Bootstrap extends React.Component<{}, any> {
301301
onScrollBeginDrag={() => {
302302
console.log('onScrollBeginDrag');
303303
}}
304+
onScrollEndDrag={() => {
305+
console.log('onScrollEndDrag');
306+
}}
304307
onScroll={() => {
305308
console.log('onScroll');
306309
}}

vnext/Microsoft.ReactNative/CompositionSwitcher.idl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ namespace Microsoft.ReactNative.Composition.Experimental
110110
void ScrollEnabled(Boolean isScrollEnabled);
111111
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollPositionChanged;
112112
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollBeginDrag;
113+
event Windows.Foundation.EventHandler<IScrollPositionChangedArgs> ScrollEndDrag;
113114
void ContentSize(Windows.Foundation.Numerics.Vector2 size);
114115
Windows.Foundation.Numerics.Vector3 ScrollPosition { get; };
115116
void ScrollBy(Windows.Foundation.Numerics.Vector3 offset, Boolean animate);

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,11 +709,18 @@ struct CompScrollerVisual : winrt::implements<
709709
m_outer->m_custom = false;
710710
m_outer->m_inertia = true;
711711
m_outer->m_currentPosition = args.NaturalRestingPosition();
712-
m_outer->FireScrollBeginDrag({args.NaturalRestingPosition().x, args.NaturalRestingPosition().y});
712+
// When the user stops interacting with the object, tracker can go into two paths:
713+
// 1. tracker goes into idle state immediately
714+
// 2. tracker has just started gliding into Inertia state
715+
// Fire ScrollEndDrag
716+
m_outer->FireScrollEndDrag({args.NaturalRestingPosition().x, args.NaturalRestingPosition().y});
713717
}
714718
void InteractingStateEntered(
715719
typename TTypeRedirects::InteractionTracker sender,
716-
typename TTypeRedirects::InteractionTrackerInteractingStateEnteredArgs args) noexcept {}
720+
typename TTypeRedirects::InteractionTrackerInteractingStateEnteredArgs args) noexcept {
721+
// Fire when the user starts dragging the object
722+
m_outer->FireScrollBeginDrag({sender.Position().x, sender.Position().y});
723+
}
717724
void RequestIgnored(
718725
typename TTypeRedirects::InteractionTracker sender,
719726
typename TTypeRedirects::InteractionTrackerRequestIgnoredArgs args) noexcept {}
@@ -939,6 +946,13 @@ struct CompScrollerVisual : winrt::implements<
939946
return m_scrollBeginDragEvent.add(handler);
940947
}
941948

949+
winrt::event_token ScrollEndDrag(
950+
winrt::Windows::Foundation::EventHandler<
951+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs> const
952+
&handler) noexcept {
953+
return m_scrollEndDragEvent.add(handler);
954+
}
955+
942956
void ScrollPositionChanged(winrt::event_token const &token) noexcept {
943957
m_scrollPositionChangedEvent.remove(token);
944958
}
@@ -947,6 +961,10 @@ struct CompScrollerVisual : winrt::implements<
947961
m_scrollBeginDragEvent.remove(token);
948962
}
949963

964+
void ScrollEndDrag(winrt::event_token const &token) noexcept {
965+
m_scrollEndDragEvent.remove(token);
966+
}
967+
950968
void ContentSize(winrt::Windows::Foundation::Numerics::float2 const &size) noexcept {
951969
m_contentSize = size;
952970
m_contentVisual.Size(size);
@@ -1021,6 +1039,10 @@ struct CompScrollerVisual : winrt::implements<
10211039
m_scrollBeginDragEvent(*this, winrt::make<CompScrollPositionChangedArgs>(position));
10221040
}
10231041

1042+
void FireScrollEndDrag(winrt::Windows::Foundation::Numerics::float2 position) noexcept {
1043+
m_scrollEndDragEvent(*this, winrt::make<CompScrollPositionChangedArgs>(position));
1044+
}
1045+
10241046
void UpdateMaxPosition() noexcept {
10251047
m_interactionTracker.MaxPosition(
10261048
{std::max<float>(m_contentSize.x - m_visualSize.x, 0),
@@ -1042,6 +1064,9 @@ struct CompScrollerVisual : winrt::implements<
10421064
winrt::event<winrt::Windows::Foundation::EventHandler<
10431065
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
10441066
m_scrollBeginDragEvent;
1067+
winrt::event<winrt::Windows::Foundation::EventHandler<
1068+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs>>
1069+
m_scrollEndDragEvent;
10451070
typename TTypeRedirects::SpriteVisual m_visual{nullptr};
10461071
typename TTypeRedirects::SpriteVisual m_contentVisual{nullptr};
10471072
typename TTypeRedirects::InteractionTracker m_interactionTracker{nullptr};

vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,23 @@ void ScrollViewComponentView::StartBringIntoView(
12431243
}
12441244
}
12451245

1246+
facebook::react::ScrollViewEventEmitter::Metrics ScrollViewComponentView::getScrollMetrics(
1247+
facebook::react::SharedViewEventEmitter const &eventEmitter,
1248+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) noexcept {
1249+
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics{};
1250+
if (eventEmitter) {
1251+
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1252+
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1253+
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1254+
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1255+
scrollMetrics.zoomScale = 1.0;
1256+
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1257+
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1258+
}
1259+
1260+
return scrollMetrics;
1261+
}
1262+
12461263
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComponentView::createVisual() noexcept {
12471264
auto visual = m_compContext.CreateSpriteVisual();
12481265
m_scrollVisual = m_compContext.CreateScrollerVisual();
@@ -1264,14 +1281,7 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
12641281
updateStateWithContentOffset();
12651282
auto eventEmitter = GetEventEmitter();
12661283
if (eventEmitter) {
1267-
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
1268-
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1269-
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1270-
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1271-
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1272-
scrollMetrics.zoomScale = 1.0;
1273-
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1274-
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1284+
auto scrollMetrics = getScrollMetrics(eventEmitter, args);
12751285
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
12761286
->onScroll(scrollMetrics);
12771287
m_lastScrollEventTime = now;
@@ -1289,19 +1299,26 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
12891299
updateStateWithContentOffset();
12901300
auto eventEmitter = GetEventEmitter();
12911301
if (eventEmitter) {
1292-
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
1293-
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1294-
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1295-
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1296-
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1297-
scrollMetrics.zoomScale = 1.0;
1298-
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1299-
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1302+
auto scrollMetrics = getScrollMetrics(eventEmitter, args);
13001303
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
13011304
->onScrollBeginDrag(scrollMetrics);
13021305
}
13031306
});
13041307

1308+
m_scrollEndDragRevoker = m_scrollVisual.ScrollEndDrag(
1309+
winrt::auto_revoke,
1310+
[this](
1311+
winrt::IInspectable const & /*sender*/,
1312+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1313+
updateStateWithContentOffset();
1314+
auto eventEmitter = GetEventEmitter();
1315+
if (eventEmitter) {
1316+
auto scrollMetrics = getScrollMetrics(eventEmitter, args);
1317+
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
1318+
->onScrollEndDrag(scrollMetrics);
1319+
}
1320+
});
1321+
13051322
return visual;
13061323
}
13071324

vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
129129
bool scrollRight(float delta, bool animate) noexcept;
130130
void updateBackgroundColor(const facebook::react::SharedColor &color) noexcept;
131131
void updateStateWithContentOffset() noexcept;
132+
facebook::react::ScrollViewEventEmitter::Metrics getScrollMetrics(
133+
facebook::react::SharedViewEventEmitter const &eventEmitter,
134+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) noexcept;
132135
void updateShowsHorizontalScrollIndicator(bool value) noexcept;
133136
void updateShowsVerticalScrollIndicator(bool value) noexcept;
134137

@@ -141,6 +144,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
141144
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollBeginDrag_revoker
142145
m_scrollBeginDragRevoker{};
143146

147+
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual::ScrollEndDrag_revoker
148+
m_scrollEndDragRevoker{};
149+
144150
float m_zoomFactor{1.0f};
145151
bool m_isScrollingFromInertia = false;
146152
bool m_isScrolling = false;

0 commit comments

Comments
 (0)