diff --git a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs index b5d7d3896df2..9849c83d0ec4 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs @@ -12,6 +12,7 @@ public class RecyclerViewScrollListener : Recycler int _horizontalOffset, _verticalOffset; TItemsView _itemsView; readonly bool _getCenteredItemOnXAndY = false; + bool _hasCompletedFirstLayout = false; public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter itemsViewAdapter) : this(itemsView, itemsViewAdapter, false) { @@ -28,6 +29,8 @@ public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter itemsViewAdapter) { ItemsViewAdapter = itemsViewAdapter; + // Reset flag when adapter changes to handle ItemsSource updates + _hasCompletedFirstLayout = false; } public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) @@ -41,6 +44,17 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) _horizontalOffset += dx; _verticalOffset += dy; + // Prevent the Scrolled event from firing on the very first layout callback only. + // This is the initial OnScrolled(0,0) call when the view is first laid out. + // After that, layout is marked as complete and all subsequent scroll events are allowed. + if (!_hasCompletedFirstLayout && !recyclerView.IsLaidOut && dx == 0 && dy == 0) + { + return; + } + + // Mark that first layout has been processed - all future scrolls should fire events + _hasCompletedFirstLayout = true; + var (First, Center, Last) = GetVisibleItemsIndex(recyclerView); var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs { diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue33333.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue33333.cs new file mode 100644 index 000000000000..593be9c7db35 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue33333.cs @@ -0,0 +1,65 @@ +using System.Collections.ObjectModel; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Graphics; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 33333, "CollectionView Scrolled event is triggered on the initial app load", PlatformAffected.Android)] +public class Issue33333 : ContentPage +{ + int _scrolledEventCount = 0; + + public Issue33333() + { + var items = new ObservableCollection(); + for (int i = 0; i < 50; i++) + { + items.Add($"Item {i}"); + } + + var scrollCountLabel = new Label + { + AutomationId = "ScrollCountLabel", + Text = "Scrolled Event Count: 0", + FontSize = 18, + Padding = new Thickness(10), + BackgroundColor = Colors.LightYellow + }; + + var collectionView = new CollectionView + { + AutomationId = "TestCollectionView", + ItemsSource = items, + ItemTemplate = new DataTemplate(() => + { + var label = new Label + { + Padding = new Thickness(10), + VerticalOptions = LayoutOptions.Center + }; + label.SetBinding(Label.TextProperty, "."); + return label; + }) + }; + + collectionView.Scrolled += (sender, e) => + { + _scrolledEventCount++; + scrollCountLabel.Text = $"Scrolled Event Count: {_scrolledEventCount}"; + }; + + var instructionLabel = new Label + { + AutomationId = "InstructionLabel", + Text = "The Scrolled event count should remain 0 on initial load. On Android, it incorrectly fires immediately.", + Padding = new Thickness(10), + FontSize = 14, + TextColor = Colors.Gray + }; + + Content = new StackLayout + { + Children = { scrollCountLabel, instructionLabel, collectionView } + }; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33333.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33333.cs new file mode 100644 index 000000000000..c6b4ea31d6e5 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33333.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue33333 : _IssuesUITest +{ + public override string Issue => "CollectionView Scrolled event is triggered on the initial app load"; + + public Issue33333(TestDevice device) : base(device) { } + + [Test] + [Category(UITestCategories.CollectionView)] + public void CollectionViewScrolledEventShouldNotFireOnInitialLoad() + { + App.WaitForElement("ScrollCountLabel"); + + var scrollCountText = App.FindElement("ScrollCountLabel").GetText(); + Assert.That(scrollCountText, Is.EqualTo("Scrolled Event Count: 0"), + "Scrolled event should not fire on initial load without user interaction"); + } +} \ No newline at end of file