|
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 {} |
@@ -1435,4 +1453,33 @@ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noe |
1435 | 1453 | void ScrollViewComponentView::updateDecelerationRate(float value) noexcept { |
1436 | 1454 | m_scrollVisual.SetDecelerationRate({value, value, value}); |
1437 | 1455 | } |
| 1456 | + |
| 1457 | +void ScrollViewComponentView::updateSnapPoints() noexcept { |
| 1458 | + const auto &viewProps = *std::static_pointer_cast<const facebook::react::ScrollViewProps>(this->viewProps()); |
| 1459 | + const auto snapToOffsets = CreateSnapToOffsets(viewProps.snapToOffsets); |
| 1460 | + |
| 1461 | + // snapToOffsets has priority over snapToInterval (matches React Native behavior) |
| 1462 | + if (viewProps.snapToInterval > 0) { |
| 1463 | + // Generate snap points based on interval |
| 1464 | + // Calculate the content size to determine how many intervals to create |
| 1465 | + float contentLength = viewProps.horizontal |
| 1466 | + ? std::max(m_contentSize.width, m_layoutMetrics.frame.size.width) * m_layoutMetrics.pointScaleFactor |
| 1467 | + : std::max(m_contentSize.height, m_layoutMetrics.frame.size.height) * m_layoutMetrics.pointScaleFactor; |
| 1468 | + |
| 1469 | + float interval = static_cast<float>(viewProps.snapToInterval) * m_layoutMetrics.pointScaleFactor; |
| 1470 | + |
| 1471 | + // Ensure we have a reasonable minimum interval to avoid infinite loops or excessive memory usage |
| 1472 | + if (interval >= 1.0f && contentLength > 0) { |
| 1473 | + // Generate offsets at each interval, but limit the number of snap points to avoid excessive memory usage |
| 1474 | + int snapPointCount = 0; |
| 1475 | + |
| 1476 | + for (float offset = 0; offset <= contentLength && snapPointCount < c_maxSnapPoints; offset += interval) { |
| 1477 | + snapToOffsets.Append(offset); |
| 1478 | + snapPointCount++; |
| 1479 | + } |
| 1480 | + } |
| 1481 | + } |
| 1482 | + |
| 1483 | + m_scrollVisual.SetSnapPoints(viewProps.snapToStart, viewProps.snapToEnd, snapToOffsets.GetView()); |
| 1484 | +} |
1438 | 1485 | } // namespace winrt::Microsoft::ReactNative::Composition::implementation |
0 commit comments