private void OnEffectiveViewportChanged()

in src/Avalonia.Controls/VirtualizingStackPanel.cs [955:1096]


        private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
        {
            var vertical = Orientation == Orientation.Vertical;
            var oldViewportStart = vertical ? _viewport.Top : _viewport.Left;
            var oldViewportEnd = vertical ? _viewport.Bottom : _viewport.Right;
            var oldExtendedViewportStart = vertical ? _extendedViewport.Top : _extendedViewport.Left;
            var oldExtendedViewportEnd = vertical ? _extendedViewport.Bottom : _extendedViewport.Right;

            // Update current viewport
            _viewport = e.EffectiveViewport.Intersect(new(Bounds.Size));
            _isWaitingForViewportUpdate = false;

            // Calculate buffer sizes based on viewport dimensions
            var viewportSize = vertical ? _viewport.Height : _viewport.Width;
            var bufferSize = viewportSize * _bufferFactor;
            
            // Calculate extended viewport with relative buffers
            var extendedViewportStart = vertical ? 
                Math.Max(0, _viewport.Top - bufferSize) : 
                Math.Max(0, _viewport.Left - bufferSize);
                
            var extendedViewportEnd = vertical ? 
                Math.Min(Bounds.Height, _viewport.Bottom + bufferSize) : 
                Math.Min(Bounds.Width, _viewport.Right + bufferSize);

            // special case:
            // If we are at the start of the list, append 2 * CacheLength additional items
            // If we are at the end of the list, prepend 2 * CacheLength additional items
            // - this way we always maintain "2 * CacheLength * element" items. 
            if (vertical)
            {
                var spaceAbove = _viewport.Top - bufferSize;
                var spaceBelow = Bounds.Height - (_viewport.Bottom + bufferSize);
                
                if (spaceAbove < 0 && spaceBelow >= 0)
                    extendedViewportEnd = Math.Min(Bounds.Height, extendedViewportEnd + Math.Abs(spaceAbove));
                if (spaceAbove >= 0 && spaceBelow < 0)
                    extendedViewportStart = Math.Max(0, extendedViewportStart - Math.Abs(spaceBelow));
            }
            else
            {
                var spaceLeft = _viewport.Left - bufferSize;
                var spaceRight = Bounds.Width - (_viewport.Right + bufferSize);
                
                if (spaceLeft < 0 && spaceRight >= 0)
                    extendedViewportEnd = Math.Min(Bounds.Width, extendedViewportEnd + Math.Abs(spaceLeft));
                if(spaceLeft >= 0 && spaceRight < 0)
                    extendedViewportStart = Math.Max(0, extendedViewportStart - Math.Abs(spaceRight));
            }

            Rect extendedViewPort;
            if (vertical)
            {
                extendedViewPort = new Rect(
                    _viewport.X, 
                    extendedViewportStart,
                    _viewport.Width,
                    extendedViewportEnd - extendedViewportStart);
            }
            else
            {
                extendedViewPort = new Rect(
                    extendedViewportStart,
                    _viewport.Y,
                    extendedViewportEnd - extendedViewportStart,
                    _viewport.Height);
            }

            // Determine if we need a new measure
            var newViewportStart = vertical ? _viewport.Top : _viewport.Left;
            var newViewportEnd = vertical ? _viewport.Bottom : _viewport.Right;
            var newExtendedViewportStart = vertical ? extendedViewPort.Top : extendedViewPort.Left;
            var newExtendedViewportEnd = vertical ? extendedViewPort.Bottom : extendedViewPort.Right;

            var needsMeasure = false;
            
           
            // Case 1: Viewport has changed significantly
            if (!MathUtilities.AreClose(oldViewportStart, newViewportStart) ||
                !MathUtilities.AreClose(oldViewportEnd, newViewportEnd))
            {
                // Case 1a: The new viewport exceeds the old extended viewport
                if (newViewportStart < oldExtendedViewportStart || 
                    newViewportEnd > oldExtendedViewportEnd)
                {
                    needsMeasure = true;
                }
                // Case 1b: The extended viewport has changed significantly
                else if (!MathUtilities.AreClose(oldExtendedViewportStart, newExtendedViewportStart) ||
                         !MathUtilities.AreClose(oldExtendedViewportEnd, newExtendedViewportEnd))
                {
                    // Check if we're about to scroll into an area where we don't have realized elements
                    // This would be the case if we're near the edge of our current extended viewport
                    var nearingEdge = false;
                    
                    if (_realizedElements != null)
                    {
                        var firstRealizedElementU = _realizedElements.StartU;
                        var lastRealizedElementU = _realizedElements.StartU;
                        
                        for (var i = 0; i < _realizedElements.Count; i++)
                        {
                            lastRealizedElementU += _realizedElements.SizeU[i];
                        }
                        
                        // If scrolling up/left and nearing the top/left edge of realized elements
                        if (newViewportStart < oldViewportStart && 
                            newViewportStart - newExtendedViewportStart < bufferSize)
                        {
                            // Edge case: We're at item 0 with excess measurement space.
                            // Skip re-measuring since we're at the list start and it won't change the result.
                            // This prevents redundant Measure-Arrange cycles when at list beginning.
                            nearingEdge = !_hasReachedStart;
                        }
                        
                        // If scrolling down/right and nearing the bottom/right edge of realized elements
                        if (newViewportEnd > oldViewportEnd && 
                            newExtendedViewportEnd - newViewportEnd < bufferSize)
                        {
                            // Edge case: We're at the last item with excess measurement space.
                            // Skip re-measuring since we're at the list end and it won't change the result.
                            // This prevents redundant Measure-Arrange cycles when at list beginning.
                            nearingEdge = !_hasReachedEnd;
                        }
                    }
                    else
                    {
                        nearingEdge = true;
                    }
                    
                    needsMeasure = nearingEdge;
                }
            }

            if (needsMeasure)
            {
                // only store the new "old" extended viewport if we _did_ actually measure
                _extendedViewport = extendedViewPort;
                
                InvalidateMeasure();
            }
        }