Skip to content

Commit a7a039c

Browse files
anupriya13Protik Biswas
authored andcommitted
[Fabric] Implement snapToInterval property for ScrollView (#14847)
1 parent 8420be7 commit a7a039c

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
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 snapToInterval property for Fabric ScrollView",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace winrt::Microsoft::ReactNative::Composition::implementation {
2828

2929
constexpr float c_scrollerLineDelta = 16.0f;
30+
constexpr auto c_maxSnapPoints = 1000;
3031

3132
enum class ScrollbarHitRegion : int {
3233
Unknown = -1,
@@ -740,6 +741,15 @@ void ScrollViewComponentView::updateBackgroundColor(const facebook::react::Share
740741
}
741742
}
742743

744+
winrt::Windows::Foundation::Collections::IVector<float> ScrollViewComponentView::CreateSnapToOffsets(
745+
const std::vector<float> &offsets) {
746+
auto snapToOffsets = winrt::single_threaded_vector<float>();
747+
for (const auto &offset : offsets) {
748+
snapToOffsets.Append(offset);
749+
}
750+
return snapToOffsets;
751+
}
752+
743753
void ScrollViewComponentView::updateProps(
744754
facebook::react::Props::Shared const &props,
745755
facebook::react::Props::Shared const &oldProps) noexcept {
@@ -807,12 +817,17 @@ void ScrollViewComponentView::updateProps(
807817
}
808818

809819
if (oldViewProps.snapToStart != newViewProps.snapToStart || oldViewProps.snapToEnd != newViewProps.snapToEnd ||
810-
oldViewProps.snapToOffsets != newViewProps.snapToOffsets) {
811-
const auto snapToOffsets = winrt::single_threaded_vector<float>();
812-
for (const auto &offset : newViewProps.snapToOffsets) {
813-
snapToOffsets.Append(static_cast<float>(offset));
820+
oldViewProps.snapToOffsets != newViewProps.snapToOffsets ||
821+
oldViewProps.snapToInterval != newViewProps.snapToInterval) {
822+
if ((newViewProps.snapToInterval > 0 || oldViewProps.snapToInterval != newViewProps.snapToInterval) &&
823+
(newViewProps.decelerationRate >= 0.99)) {
824+
// Use the comprehensive updateSnapPoints method when snapToInterval is involved
825+
// Typically used in combination with snapToAlignment and decelerationRate="fast".
826+
updateSnapPoints();
827+
} else {
828+
auto snapToOffsets = CreateSnapToOffsets(newViewProps.snapToOffsets);
829+
m_scrollVisual.SetSnapPoints(newViewProps.snapToStart, newViewProps.snapToEnd, snapToOffsets.GetView());
814830
}
815-
m_scrollVisual.SetSnapPoints(newViewProps.snapToStart, newViewProps.snapToEnd, snapToOffsets.GetView());
816831
}
817832
}
818833

@@ -863,6 +878,9 @@ void ScrollViewComponentView::updateContentVisualSize() noexcept {
863878
m_verticalScrollbarComponent->ContentSize(contentSize);
864879
m_horizontalScrollbarComponent->ContentSize(contentSize);
865880
m_scrollVisual.ContentSize(contentSize);
881+
882+
// Update snap points if snapToInterval is being used, as content size affects the number of snap points
883+
updateSnapPoints();
866884
}
867885

868886
void ScrollViewComponentView::prepareForRecycle() noexcept {}
@@ -1461,4 +1479,33 @@ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noe
14611479
void ScrollViewComponentView::updateDecelerationRate(float value) noexcept {
14621480
m_scrollVisual.SetDecelerationRate({value, value, value});
14631481
}
1482+
1483+
void ScrollViewComponentView::updateSnapPoints() noexcept {
1484+
const auto &viewProps = *std::static_pointer_cast<const facebook::react::ScrollViewProps>(this->viewProps());
1485+
const auto snapToOffsets = CreateSnapToOffsets(viewProps.snapToOffsets);
1486+
1487+
// snapToOffsets has priority over snapToInterval (matches React Native behavior)
1488+
if (viewProps.snapToInterval > 0) {
1489+
// Generate snap points based on interval
1490+
// Calculate the content size to determine how many intervals to create
1491+
float contentLength = viewProps.horizontal
1492+
? std::max(m_contentSize.width, m_layoutMetrics.frame.size.width) * m_layoutMetrics.pointScaleFactor
1493+
: std::max(m_contentSize.height, m_layoutMetrics.frame.size.height) * m_layoutMetrics.pointScaleFactor;
1494+
1495+
float interval = static_cast<float>(viewProps.snapToInterval) * m_layoutMetrics.pointScaleFactor;
1496+
1497+
// Ensure we have a reasonable minimum interval to avoid infinite loops or excessive memory usage
1498+
if (interval >= 1.0f && contentLength > 0) {
1499+
// Generate offsets at each interval, but limit the number of snap points to avoid excessive memory usage
1500+
int snapPointCount = 0;
1501+
1502+
for (float offset = 0; offset <= contentLength && snapPointCount < c_maxSnapPoints; offset += interval) {
1503+
snapToOffsets.Append(offset);
1504+
snapPointCount++;
1505+
}
1506+
}
1507+
}
1508+
1509+
m_scrollVisual.SetSnapPoints(viewProps.snapToStart, viewProps.snapToEnd, snapToOffsets.GetView());
1510+
}
14641511
} // namespace winrt::Microsoft::ReactNative::Composition::implementation

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
121121
private:
122122
void updateDecelerationRate(float value) noexcept;
123123
void updateContentVisualSize() noexcept;
124+
void updateSnapPoints() noexcept;
124125
bool scrollToEnd(bool animate) noexcept;
125126
bool scrollToStart(bool animate) noexcept;
126127
bool scrollDown(float delta, bool animate) noexcept;
@@ -134,6 +135,7 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
134135
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) noexcept;
135136
void updateShowsHorizontalScrollIndicator(bool value) noexcept;
136137
void updateShowsVerticalScrollIndicator(bool value) noexcept;
138+
winrt::Windows::Foundation::Collections::IVector<float> CreateSnapToOffsets(const std::vector<float> &offsets);
137139

138140
facebook::react::Size m_contentSize;
139141
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollVisual m_scrollVisual{nullptr};

0 commit comments

Comments
 (0)