[Android] Fix CollectionView LinearItemsLayout first/last items clipped when ItemSpacing changes at runtime#34664
Conversation
|
Hey there @@Shalini-Ashokan! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
🚦 Gate — Test Verification📊 Expand Full Gate —
|
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34664 | Add _removeOuterEdgeSpacing flag; skip edge-zeroing for LinearItemsLayout via early return |
❌ Gate FAILED | SpacingItemDecoration.cs |
Gate failed on Android screenshot test |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34664 | _removeOuterEdgeSpacing flag in SpacingItemDecoration; early return for Linear; keeps negative RecyclerView padding |
❌ Gate FAILED | 1 fix file | Gate failed — screenshot mismatch or timing |
| 1 | Attempt 1 (claude-opus-4.6) | Edge-zeroing applied to ALL layouts (Grid + Linear) + remove negative padding entirely from MauiRecyclerView | ✅ PASS | 2 files | Simpler — no flag, no branching |
| 2 | Attempt 2 (claude-sonnet-4.6) | Direct per-side ternary assignment (top = row==0?0:V) for all layouts, remove negative padding |
✅ PASS | 2 files | Clean single-pass, no conditionals |
| 3 | Attempt 3 (gpt-5.3-codex) | Only remove negative padding from MauiRecyclerView for LinearItemsLayout | ❌ BLOCKED | 1 file | Unrelated CS0246 build error |
| 4 | Attempt 4 (gpt-5.4) | Only remove negative padding from MauiRecyclerView for LinearItemsLayout (minimal) | ❌ BLOCKED | 1 file | Same CS0246 build error |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | NO NEW IDEAS |
| claude-sonnet-4.6 | 2 | Yes | Position-aware edge suppression for Linear only — effectively covered by Attempt 2 |
| gpt-5.3-codex | 2 | Yes | RecyclerView ClipToPadding=false strategy — significant refactor, too invasive for a regression fix |
Exhausted: Yes
Selected Fix: Attempt 1 (claude-opus-4.6) — Apply edge-zeroing to ALL layouts in SpacingItemDecoration + remove negative padding from MauiRecyclerView.
Reason over PR's fix: Attempt 1 is a cleaner, more uniform solution. The PR's fix adds a conditional boolean flag that perpetuates the two-mechanism design (negative padding + selective edge-zeroing). Attempt 1 removes the dual-mechanism entirely, handling outer-edge spacing in one consistent place. Gate failed for PR's fix — attempts 1 and 2 both passed the same screenshot test.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #34636 identified, regression from PR #27093 |
| Gate | ❌ FAILED | Android screenshot test failed |
| Try-Fix | ✅ COMPLETE | 4 attempts: 2 passing, 2 blocked (env build error) |
| Report | ✅ COMPLETE |
Summary
PR #34664 fixes a regression (from PR #27093 on the inflight/candate branch) where CollectionView LinearItemsLayout first/last items become clipped after changing ItemSpacing at runtime. The root cause is a double-compensation conflict: PR #27093 zeroed outer-edge offsets for all layouts in SpacingItemDecoration.GetItemOffsets, while MauiRecyclerView.UpdateItemSpacing still applies negative RecyclerView padding for linear layouts. This combination pulls first/last items outside the clip boundary.
The gate failed on Android, and two independent alternative fixes (Attempts 1 and 2) both passed the same screenshot test, suggesting the PR's specific approach produces a subtly different rendering from what the committed snapshot captures.
Root Cause
MauiRecyclerView.UpdateItemSpacing applies SetPadding(-offset, -offset, -offset, -offset) for LinearItemsLayout, relying on SpacingItemDecoration.GetItemOffsets to add a matching positive offset at the outer edges to cancel the negative padding. PR #27093 removed the positive outer-edge offset (zeroing it) but left the negative padding intact, causing the net outer offset for first/last items to be negative — pulling them outside the visible area.
Fix Quality
PR's Fix Issues:
- Gate Failed — The screenshot test did not pass with the PR's changes on the gate's Android device, even though a snapshot was committed
- Perpetuates dual-mechanism design — The fix keeps the negative RecyclerView padding for LinearItemsLayout, which must now be carefully kept in sync with the
_removeOuterEdgeSpacingflag inSpacingItemDecoration - Test robustness —
VerifyScreenshot()called withoutretryTimeoutafter a spacing change that triggers CollectionView re-layout; this can cause flaky failures if layout hasn't settled before the screenshot - Snapshot quality — No
retryTimeoutin the test suggests the snapshot may have been captured before the animation/layout completed
Better Alternative Found:
Attempt 1 (claude-opus-4.6) passed the same test with a cleaner, symmetric approach:
- Apply edge-zeroing to ALL layout types (not just Grid) in
SpacingItemDecoration.GetItemOffsets - Remove negative padding from
MauiRecyclerView.UpdateItemSpacingentirely (SetPadding(0, 0, 0, 0)always) - This eliminates the dual-mechanism entirely — outer-edge spacing handled in one place with no conditional branching
Suggested Changes:
-
SpacingItemDecoration.cs: Remove the_removeOuterEdgeSpacingflag; keepGetItemOffsetsedge-zeroing for all layout types (the existing logic already works for Linear with span=1) -
MauiRecyclerView.cs: Remove conditional negative padding; always useSetPadding(0, 0, 0, 0) -
Issue34636.cs(test): AddretryTimeouttoVerifyScreenshot()to prevent flaky failures after layout:VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));
-
Snapshot: Regenerate the Android snapshot with the updated test.
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
CollectionView with LinearItemsLayout, the first and last items become truncated/clipped after changing the ItemSpacing value at runtime. This affects both vertical and horizontal list orientations on Android.
Note: This is inflight/candate branch changes where exsiting PR #27093 causes the regression on this branch. So added this new fix to overcome the regression.
Root Cause
PR #27093 changed SpacingItemDecoration.GetItemOffsets to zero out the outer edge offsets (outRect.Top = 0 for item 0, outRect.Bottom = 0 for last item) for all layout types including LinearItemsLayout. However, MauiRecyclerView.UpdateItemSpacing still applied negative padding (SetPadding(-offset, ...)) on the RecyclerView for linear layouts. This conflict caused the first and last items to be pulled outside the visible area and clipped, since the positive offset that was supposed to cancel the negative padding was removed.
Description of Change
Added a _removeOuterEdgeSpacing boolean flag to SpacingItemDecoration, set to true only for GridItemsLayout. In GetItemOffsets, an early return is added when _removeOuterEdgeSpacing is false, skipping the edge-zeroing logic for LinearItemsLayout. This restores the correct balanced behavior for linear lists: the positive outRect offset (+N) and the negative RecyclerView padding (-N) cancel each other out at the edges, keeping the first and last items correctly positioned at the screen boundary.
Validated the behavior in the following platforms
Issues Fixed
Fixes #34636
Output ScreenShot