diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 2baf3d6147..65e3adf3dc 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -858,7 +858,32 @@ namespace Avalonia.Controls // Estimate the element size. var estimatedSize = EstimateElementSizeU(); - // TODO: Use _startU to work this out. + // If we have a valid StartU, use it to anchor estimates relative to the realized range. + if (_realizedElements is { } realized && !double.IsNaN(realized.StartU)) + { + var first = realized.FirstIndex; + var last = realized.LastIndex; + + if (index < first) + { + return realized.StartU - ((first - index) * estimatedSize); + } + + if (index > last) + { + var sizes = realized.SizeU; + var realizedSpan = 0.0; + + for (var i = 0; i < sizes.Count; ++i) + { + var sizeU = sizes[i]; + realizedSpan += double.IsNaN(sizeU) ? estimatedSize : sizeU; + } + + return realized.StartU + realizedSpan + ((index - last - 1) * estimatedSize); + } + } + return index * estimatedSize; } diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index 3dffc5d47e..89eaab6f66 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -1553,6 +1553,40 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 125, 100, 25), container.Bounds); } + [Fact] + public void Focused_Container_Is_Positioned_Correctly_When_Scrolled_Past_Items_With_Different_Heights() + { + using var app = App(); + + var items = Enumerable.Range(0, 20) + .Select(x => new ItemWithHeight(x, x < 10 ? 10 : 50)) + .ToList(); + + var (target, _, _) = CreateTarget(items: items, itemTemplate: CanvasWithHeightTemplate); + + var focused = Assert.IsType(target.ContainerFromIndex(5)); + focused.Focusable = true; + focused.Focus(); + + target.ScrollIntoView(15); + Layout(target); + + Assert.True(target.FirstRealizedIndex > 5); + + var firstIndex = target.FirstRealizedIndex; + var firstRealized = Assert.IsType(target.ContainerFromIndex(firstIndex)); + var realized = target.GetRealizedElements() + .Where(x => x is not null) + .Cast() + .ToList(); + + var estimatedSize = realized.Average(x => x.DesiredSize.Height); + var expectedTop = firstRealized.Bounds.Top - ((firstIndex - 5) * estimatedSize); + + focused = Assert.IsType(target.ContainerFromIndex(5)); + Assert.Equal(expectedTop, focused.Bounds.Top, 3); + } + [Theory] [InlineData(0d, 4, 7,