|
27 | 27 | namespace winrt::Microsoft::ReactNative::Composition::implementation { |
28 | 28 |
|
29 | 29 | constexpr float c_scrollerLineDelta = 16.0f; |
| 30 | +constexpr auto c_maxSnapPoints = 1000; |
30 | 31 |
|
31 | 32 | enum class ScrollbarHitRegion : int { |
32 | 33 | Unknown = -1, |
@@ -740,6 +741,15 @@ void ScrollViewComponentView::updateBackgroundColor(const facebook::react::Share |
740 | 741 | } |
741 | 742 | } |
742 | 743 |
|
| 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 | + |
743 | 753 | void ScrollViewComponentView::updateProps( |
744 | 754 | facebook::react::Props::Shared const &props, |
745 | 755 | facebook::react::Props::Shared const &oldProps) noexcept { |
@@ -807,12 +817,17 @@ void ScrollViewComponentView::updateProps( |
807 | 817 | } |
808 | 818 |
|
809 | 819 | 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()); |
814 | 830 | } |
815 | | - m_scrollVisual.SetSnapPoints(newViewProps.snapToStart, newViewProps.snapToEnd, snapToOffsets.GetView()); |
816 | 831 | } |
817 | 832 | } |
818 | 833 |
|
@@ -863,6 +878,9 @@ void ScrollViewComponentView::updateContentVisualSize() noexcept { |
863 | 878 | m_verticalScrollbarComponent->ContentSize(contentSize); |
864 | 879 | m_horizontalScrollbarComponent->ContentSize(contentSize); |
865 | 880 | 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(); |
866 | 884 | } |
867 | 885 |
|
868 | 886 | void ScrollViewComponentView::prepareForRecycle() noexcept {} |
@@ -1461,4 +1479,33 @@ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noe |
1461 | 1479 | void ScrollViewComponentView::updateDecelerationRate(float value) noexcept { |
1462 | 1480 | m_scrollVisual.SetDecelerationRate({value, value, value}); |
1463 | 1481 | } |
| 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 | +} |
1464 | 1511 | } // namespace winrt::Microsoft::ReactNative::Composition::implementation |
0 commit comments