-
Notifications
You must be signed in to change notification settings - Fork 2k
March 2, 2026 Candidate Branch #34294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
021053a
d93eb70
47b9d94
dc75f76
5d71c59
7f3e760
bbd7f28
566fff2
9dedb99
6886d6f
445fcd6
92dc834
c0e0d9b
b86cba5
a66efa2
f282ff5
985551b
7f9f536
377a4a1
6b681a7
8634e1d
ad9b09a
cd0bc47
6d8bfc8
3014914
7f6db9c
3d0469e
aa174b5
9bc78e2
2e79d00
ab215d0
81dbe0e
25dd365
adfa470
1cf83b9
ad51bb6
f118ecc
1f0bc7d
4320975
3a8ff02
6832883
c60fccb
6e1f847
ee3b9ad
3f9a1dd
2c73f63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ public class RecyclerViewScrollListener<TItemsView, TItemsViewSource> : Recycler | |
| int _horizontalOffset, _verticalOffset; | ||
| TItemsView _itemsView; | ||
| readonly bool _getCenteredItemOnXAndY = false; | ||
| bool _hasCompletedFirstLayout = false; | ||
|
|
||
| public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter<TItemsView, TItemsViewSource> itemsViewAdapter) : this(itemsView, itemsViewAdapter, false) | ||
| { | ||
|
|
@@ -28,6 +29,8 @@ public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter<TItemsV | |
| internal void UpdateAdapter(ItemsViewAdapter<TItemsView, TItemsViewSource> itemsViewAdapter) | ||
| { | ||
| ItemsViewAdapter = itemsViewAdapter; | ||
| // Reset flag when adapter changes to handle ItemsSource updates | ||
| _hasCompletedFirstLayout = false; | ||
| } | ||
|
|
||
| public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) | ||
|
|
@@ -38,6 +41,17 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) | |
| _horizontalOffset = itemCount == 0 ? 0 : _horizontalOffset + dx; | ||
| _verticalOffset = itemCount == 0 ? 0 : _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 | ||
| { | ||
|
|
@@ -54,21 +68,30 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy) | |
|
|
||
| // Don't send RemainingItemsThresholdReached event for non-linear layout managers | ||
| // This can also happen if a layout pass has not happened yet | ||
| if (Last == -1) | ||
| if (Last == -1 || ItemsViewAdapter is null || _itemsView.RemainingItemsThreshold == -1) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var itemsSource = ItemsViewAdapter.ItemsSource; | ||
| int headerValue = itemsSource.HasHeader ? 1 : 0; | ||
| int footerValue = itemsSource.HasFooter ? 1 : 0; | ||
|
|
||
| // Calculate actual data item count (excluding header and footer positions) | ||
| int actualItemCount = ItemsViewAdapter.ItemCount - footerValue - headerValue; | ||
|
|
||
| // Ensure we're within the data items region (not in header/footer) | ||
| if (Last < headerValue || Last > actualItemCount) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Check if we're at or within threshold distance from the last data item | ||
| bool isThresholdReached = (Last == actualItemCount - 1) || (actualItemCount - 1 - Last <= _itemsView.RemainingItemsThreshold); | ||
|
|
||
| switch (_itemsView.RemainingItemsThreshold) | ||
| if (isThresholdReached) | ||
| { | ||
| case -1: | ||
| return; | ||
| case 0: | ||
| if (Last == ItemsViewAdapter.ItemsSource.Count - 1) | ||
| _itemsView.SendRemainingItemsThresholdReached(); | ||
| break; | ||
| default: | ||
| if (ItemsViewAdapter.ItemsSource.Count - 1 - Last <= _itemsView.RemainingItemsThreshold) | ||
| _itemsView.SendRemainingItemsThresholdReached(); | ||
| break; | ||
| _itemsView.SendRemainingItemsThresholdReached(); | ||
| } | ||
|
Comment on lines
+76
to
95
|
||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OnInterceptTouchEventreturnstruewhen disabled (intercepts), butOnTouchEventreturnsfalse(doesn't handle). This combination can lead to inconsistent/undefined touch dispatch (e.g., event intercepted from children but not consumed). If the intent is to block interaction, prefer consuming touches when disabled (typically returntruefromOnTouchEventas well), or avoid intercepting altogether.