Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,9 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
{
base.OnScrolled(recyclerView, dx, dy);

// TODO: These offsets will be incorrect upon row size or count change.
// They are currently provided in place of LayoutManager's default offset calculation
// because it does not report accurate values in the presence of uneven rows.
// See https://stackoverflow.com/questions/27507715/android-how-to-get-the-current-x-offset-of-recyclerview
_horizontalOffset += dx;
_verticalOffset += dy;
var itemCount = recyclerView.GetAdapter()?.ItemCount ?? 0;
_horizontalOffset = itemCount == 0 ? 0 : _horizontalOffset + dx;
_verticalOffset = itemCount == 0 ? 0 : _verticalOffset + dy;

var (First, Center, Last) = GetVisibleItemsIndex(recyclerView);
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
Expand Down
69 changes: 69 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue21708.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue21708">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Button
Grid.Row="0"
Grid.Column="0"
Margin="10"
AutomationId="Fill"
Clicked="FillButton_OnClicked"
Text="Fill" />

<Button
Grid.Row="0"
Grid.Column="2"
Margin="10"
AutomationId="Empty"
Clicked="EmptyButton_OnClicked"
Text="Empty" />

<Label
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: this line uses spaces while others use tabs. The entire file should use consistent tab indentation.

Suggested change
<Label
<Label

Copilot uses AI. Check for mistakes.
Grid.Row="1"
Grid.Column="0"
AutomationId="VerticalOffsetLabel"
Text="VerticalOffset: " />

<Label AutomationId="Label"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalTextAlignment="Start"
Grid.Column="1"
Text="{Binding VerticalOffset}" />

<CollectionView AutomationId="CollectionView"
x:Name="CollectionView"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Items}"
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XAML binding ItemsSource="{Binding Items}" is incorrect. The code-behind sets CollectionView.ItemsSource = _items directly in the constructor (line 17), so the XAML binding will have no effect. Either remove the direct assignment in the code-behind and rely on the XAML binding with a proper Items property, or remove the XAML binding and keep the code-behind assignment.

Suggested change
ItemsSource="{Binding Items}"

Copilot uses AI. Check for mistakes.
Scrolled="CollectionView_OnScrolled">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="10"
FontSize="Default"
HeightRequest="20"
HorizontalTextAlignment="Center"
Text="{Binding .}"
TextColor="RoyalBlue" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Comment on lines +3 to +67
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces while others use tabs. The entire file should use consistent tab indentation.

Suggested change
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue21708">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Grid.Row="0"
Grid.Column="0"
Margin="10"
AutomationId="Fill"
Clicked="FillButton_OnClicked"
Text="Fill" />
<Button
Grid.Row="0"
Grid.Column="2"
Margin="10"
AutomationId="Empty"
Clicked="EmptyButton_OnClicked"
Text="Empty" />
<Label
Grid.Row="1"
Grid.Column="0"
AutomationId="VerticalOffsetLabel"
Text="VerticalOffset: " />
<Label AutomationId="Label"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalTextAlignment="Start"
Grid.Column="1"
Text="{Binding VerticalOffset}" />
<CollectionView AutomationId="CollectionView"
x:Name="CollectionView"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Items}"
Scrolled="CollectionView_OnScrolled">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="10"
FontSize="Default"
HeightRequest="20"
HorizontalTextAlignment="Center"
Text="{Binding .}"
TextColor="RoyalBlue" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue21708">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Grid.Row="0"
Grid.Column="0"
Margin="10"
AutomationId="Fill"
Clicked="FillButton_OnClicked"
Text="Fill" />
<Button
Grid.Row="0"
Grid.Column="2"
Margin="10"
AutomationId="Empty"
Clicked="EmptyButton_OnClicked"
Text="Empty" />
<Label
Grid.Row="1"
Grid.Column="0"
AutomationId="VerticalOffsetLabel"
Text="VerticalOffset: " />
<Label AutomationId="Label"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalTextAlignment="Start"
Grid.Column="1"
Text="{Binding VerticalOffset}" />
<CollectionView AutomationId="CollectionView"
x:Name="CollectionView"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Items}"
Scrolled="CollectionView_OnScrolled">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="10"
FontSize="Default"
HeightRequest="20"
HorizontalTextAlignment="Center"
Text="{Binding .}"
TextColor="RoyalBlue" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

Copilot uses AI. Check for mistakes.

Comment on lines +3 to +68
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces while others use tabs. The entire file should use consistent tab indentation.

Suggested change
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue21708">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Grid.Row="0"
Grid.Column="0"
Margin="10"
AutomationId="Fill"
Clicked="FillButton_OnClicked"
Text="Fill" />
<Button
Grid.Row="0"
Grid.Column="2"
Margin="10"
AutomationId="Empty"
Clicked="EmptyButton_OnClicked"
Text="Empty" />
<Label
Grid.Row="1"
Grid.Column="0"
AutomationId="VerticalOffsetLabel"
Text="VerticalOffset: " />
<Label AutomationId="Label"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalTextAlignment="Start"
Grid.Column="1"
Text="{Binding VerticalOffset}" />
<CollectionView AutomationId="CollectionView"
x:Name="CollectionView"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Items}"
Scrolled="CollectionView_OnScrolled">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="10"
FontSize="Default"
HeightRequest="20"
HorizontalTextAlignment="Center"
Text="{Binding .}"
TextColor="RoyalBlue" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue21708">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Grid.Row="0"
Grid.Column="0"
Margin="10"
AutomationId="Fill"
Clicked="FillButton_OnClicked"
Text="Fill" />
<Button
Grid.Row="0"
Grid.Column="2"
Margin="10"
AutomationId="Empty"
Clicked="EmptyButton_OnClicked"
Text="Empty" />
<Label
Grid.Row="1"
Grid.Column="0"
AutomationId="VerticalOffsetLabel"
Text="VerticalOffset: " />
<Label AutomationId="Label"
Grid.ColumnSpan="2"
Grid.Row="1"
HorizontalTextAlignment="Start"
Grid.Column="1"
Text="{Binding VerticalOffset}" />
<CollectionView AutomationId="CollectionView"
x:Name="CollectionView"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Items}"
Scrolled="CollectionView_OnScrolled">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label
Margin="10"
FontSize="Default"
HeightRequest="20"
HorizontalTextAlignment="Center"
Text="{Binding .}"
TextColor="RoyalBlue" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

Copilot uses AI. Check for mistakes.
</ContentPage>
52 changes: 52 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue21708.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Collections.ObjectModel;

namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 21708, "CollectionView.Scrolled event offset isn't correctly reset when items change", PlatformAffected.All)]
public partial class Issue21708 : ContentPage
{
ObservableCollection<int> _items;
double _verticalOffset;
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: this line uses spaces for indentation while the rest of the file uses tabs. This should be indented with a tab to match the surrounding code.

Suggested change
double _verticalOffset;
double _verticalOffset;

Copilot uses AI. Check for mistakes.

public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}

public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}

void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}

void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}

void FillButton_OnClicked(object sender, EventArgs e)
Comment on lines +10 to +41
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces for indentation while the rest of the file uses tabs. These should be indented with tabs to match the surrounding code.

Suggested change
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +41
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces for indentation while the rest of the file uses tabs. These should be indented with tabs to match the surrounding code.

Suggested change
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +41
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces for indentation while the rest of the file uses tabs. These should be indented with tabs to match the surrounding code.

Suggested change
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +41
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces for indentation while the rest of the file uses tabs. These should be indented with tabs to match the surrounding code.

Suggested change
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)
double _verticalOffset;
public Issue21708()
{
InitializeComponent();
_items = new ObservableCollection<int>();
AddItemsToCollectionView();
CollectionView.ItemsSource = _items;
BindingContext = this;
}
public double VerticalOffset
{
get => _verticalOffset;
set
{
_verticalOffset = value;
OnPropertyChanged(nameof(VerticalOffset));
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
VerticalOffset = e.VerticalOffset;
}
void EmptyButton_OnClicked(object sender, EventArgs e)
{
_items.Clear();
}
void FillButton_OnClicked(object sender, EventArgs e)

Copilot uses AI. Check for mistakes.
{
AddItemsToCollectionView();
}

void AddItemsToCollectionView()
{
foreach (var i in Enumerable.Range(0, 50))
_items.Add(i);
}
Comment on lines +46 to +50
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: these lines use spaces for indentation while the rest of the file uses tabs. These should be indented with tabs to match the surrounding code.

Copilot uses AI. Check for mistakes.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue21708 : _IssuesUITest
{
public Issue21708(TestDevice device) : base(device) { }

public override string Issue => "CollectionView.Scrolled event offset isn't correctly reset when items change";

[Test]
[Category(UITestCategories.CollectionView)]
public void VerifyCollectionViewVerticalOffset()
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test method name VerifyCollectionViewVerticalOffset doesn't clearly describe what behavior is being verified. The test is specifically verifying that the vertical offset resets to 0 when items are removed from the collection. A more descriptive name would be VerticalOffsetResetsToZeroWhenItemsAreRemoved or VerticalOffsetResetsWhenCollectionIsCleared.

Suggested change
public void VerifyCollectionViewVerticalOffset()
public void VerticalOffsetResetsToZeroWhenItemsAreRemoved()

Copilot uses AI. Check for mistakes.
{
App.WaitForElement("Fill");
App.ScrollDown("CollectionView");
Assert.That(App.FindElement("Label").GetText(), Is.GreaterThan("0"));
App.Tap("Empty");
Assert.That(App.FindElement("Label").GetText(), Is.EqualTo("0"));
}
Comment on lines +15 to +22
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only verifies the "Empty" scenario but doesn't verify the "Fill" scenario after emptying. According to the issue description, the offset should be correctly maintained when items are both added and removed. Consider adding an additional assertion after calling App.Tap("Fill") to verify that the offset updates correctly when items are re-added and the CollectionView is scrolled again.

Copilot uses AI. Check for mistakes.
}
Loading