Skip to content

[Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views - #31790

Merged
kubaflo merged 7 commits into
dotnet:inflight/currentfrom
Dhivya-SF4094:fix-22507
Jun 30, 2026
Merged

[Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views#31790
kubaflo merged 7 commits into
dotnet:inflight/currentfrom
Dhivya-SF4094:fix-22507

Conversation

@Dhivya-SF4094

@Dhivya-SF4094 Dhivya-SF4094 commented Sep 26, 2025

Copy link
Copy Markdown
Contributor

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:

When a CarouselView is used with nested scrollable content (e.g., CollectionView), swipe gestures are not handled correctly. By default, the CarouselView intercepts all touch events, including vertical swipes, even when the intent is to scroll the child view.

Root Cause

By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and returns true, meaning it intercepts touch gestures before child views (like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by the Carousel, even when the intent is to scroll inside the nested CollectionView.
This causes a conflict: vertical gestures intended for the inner CollectionView sometimes trigger an unwanted item transition in the CarouselView.

Description of Change

The Android CarouselView touch interception logic now classifies gestures before allowing the CarouselView to intercept them:

  • Records the initial touch position on MotionEventActions.Down.
  • Waits until movement exceeds Android ViewConfiguration.ScaledTouchSlop.
  • Locks the gesture direction once per gesture by comparing horizontal and vertical movement.
  • Returns false for off-axis gestures, such as a vertical drag on a horizontal CarouselView, so child/nested content can receive the gesture.
  • Latches delegated off-axis gestures for the rest of the touch sequence so the CarouselView does not hijack later move events (for example, when the child reaches its scroll boundary).
  • Preserves disabled CarouselView behavior by deferring to the base interception path when the ItemsView is explicitly disabled.

Validated the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed:

Fixes #22507

Screenshots

Before  After 
 
22507_Android_BeforeFix.mov
  
22507_Android_AfterFix.mov

@dotnet-policy-service dotnet-policy-service Bot added community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration labels Sep 26, 2025
@jsuarezruiz

Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jsuarezruiz jsuarezruiz added platform/android area-controls-collectionview CollectionView, CarouselView, IndicatorView labels Sep 29, 2025
@jsuarezruiz

Copy link
Copy Markdown
Contributor

/rebase

@Dhivya-SF4094
Dhivya-SF4094 marked this pull request as ready for review October 1, 2025 03:54
Copilot AI review requested due to automatic review settings October 1, 2025 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes an issue where CarouselView incorrectly intercepts vertical swipe gestures on Android, preventing proper scrolling of nested scrollable content like CollectionView. The fix improves touch event handling by comparing horizontal and vertical movement to determine whether gestures should be handled by the CarouselView or delegated to child views.

Key changes:

  • Modified touch interception logic in MauiCarouselRecyclerView to distinguish between horizontal and vertical swipes
  • Added comprehensive UI tests to validate the fix across platforms
  • Ensured nested scrollable content can scroll vertically without unwanted CarouselView item transitions

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
MauiCarouselRecyclerView.cs Enhanced OnInterceptTouchEvent method with directional swipe detection logic
Issue22507.cs (HostApp) Created test page with CarouselView containing nested CollectionView for reproducing the issue
Issue22507.cs (Tests) Added automated UI test to verify vertical scrolling works correctly within CarouselView

Comment thread src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs Outdated
@jsuarezruiz

Copy link
Copy Markdown
Contributor

/azp run MAUI-UITests-public

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Comment thread src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs Outdated
Comment thread src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue22507.cs Outdated
Comment thread src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs Outdated
@sheiksyedm

Copy link
Copy Markdown
Contributor

/rebase

@rmarinho rmarinho added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Feb 18, 2026
@Dhivya-SF4094 Dhivya-SF4094 changed the title [Android] Fixed CarouselView behaves strangely when swiping vertically in view [Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views Feb 19, 2026
@kubaflo kubaflo removed the s/agent-fix-win AI found a better alternative fix than the PR label Feb 20, 2026
@Dhivya-SF4094

Copy link
Copy Markdown
Contributor Author

Addressed concerns raised in the AI summary.

@dotnet dotnet deleted a comment from rmarinho Mar 16, 2026
@kubaflo kubaflo added s/agent-review-incomplete and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues labels Mar 16, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 26, 2026
@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-win AI found a better alternative fix than the PR labels Jun 26, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@Dhivya-SF4094 — new AI review results are available based on this last commit: cd199d8. To request a fresh review after new comments or commits, comment /review rerun.

Gate Passed Confidence Medium Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 5ec887fa

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 CarouselViewTests (VerticalDragOnHorizontalCarouselIsNotIntercepted) Category=CarouselView ✅ FAIL — 1353s ✅ PASS — 231s
🔴 Without fix — 📱 CarouselViewTests (VerticalDragOnHorizontalCarouselIsNotIntercepted): FAIL ✅ · 1353s

(truncated to last 15,000 chars)

roidX.Navigation.Common.Android.dll -> Xamarin.AndroidX.Navigation.Common.Android.dll.so
  [49/133] Xamarin.AndroidX.Navigation.Fragment.dll -> Xamarin.AndroidX.Navigation.Fragment.dll.so
  [119/133] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
  [120/133] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
  [50/133] Xamarin.AndroidX.Navigation.Runtime.Android.dll -> Xamarin.AndroidX.Navigation.Runtime.Android.dll.so
  [121/133] System.Text.Json.dll -> System.Text.Json.dll.so
  [51/133] Xamarin.AndroidX.Navigation.UI.dll -> Xamarin.AndroidX.Navigation.UI.dll.so
  [122/133] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
  [123/133] System.Threading.dll -> System.Threading.dll.so
  [124/133] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
  [52/133] Xamarin.AndroidX.RecyclerView.dll -> Xamarin.AndroidX.RecyclerView.dll.so
  [125/133] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
  [126/133] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
  [53/133] Xamarin.AndroidX.SavedState.SavedState.Android.dll -> Xamarin.AndroidX.SavedState.SavedState.Android.dll.so
  [127/133] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
  [128/133] System.dll -> System.dll.so
  [129/133] netstandard.dll -> netstandard.dll.so
  [54/133] Xamarin.AndroidX.SwipeRefreshLayout.dll -> Xamarin.AndroidX.SwipeRefreshLayout.dll.so
  [130/133] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
  [55/133] Xamarin.AndroidX.ViewPager.dll -> Xamarin.AndroidX.ViewPager.dll.so
  [56/133] Xamarin.AndroidX.ViewPager2.dll -> Xamarin.AndroidX.ViewPager2.dll.so
  [131/133] Java.Interop.dll -> Java.Interop.dll.so
  [57/133] Xamarin.Google.Android.Material.dll -> Xamarin.Google.Android.Material.dll.so
  [58/133] Xamarin.GooglePlayServices.Base.dll -> Xamarin.GooglePlayServices.Base.dll.so
  [59/133] Xamarin.GooglePlayServices.Basement.dll -> Xamarin.GooglePlayServices.Basement.dll.so
  [60/133] Xamarin.GooglePlayServices.Maps.dll -> Xamarin.GooglePlayServices.Maps.dll.so
  [61/133] Xamarin.GooglePlayServices.Tasks.dll -> Xamarin.GooglePlayServices.Tasks.dll.so
  [62/133] Xamarin.Kotlin.StdLib.dll -> Xamarin.Kotlin.StdLib.dll.so
  [132/133] Mono.Android.dll -> Mono.Android.dll.so
  [63/133] Xamarin.KotlinX.Coroutines.Core.Jvm.dll -> Xamarin.KotlinX.Coroutines.Core.Jvm.dll.so
  [64/133] Xamarin.KotlinX.Serialization.Core.Jvm.dll -> Xamarin.KotlinX.Serialization.Core.Jvm.dll.so
  [65/133] xunit.abstractions.dll -> xunit.abstractions.dll.so
  [66/133] xunit.assert.dll -> xunit.assert.dll.so
  [67/133] xunit.core.dll -> xunit.core.dll.so
  [68/133] xunit.execution.dotnet.dll -> xunit.execution.dotnet.dll.so
  [69/133] xunit.runner.utility.netcoreapp10.dll -> xunit.runner.utility.netcoreapp10.dll.so
  [70/133] System.Collections.Concurrent.dll -> System.Collections.Concurrent.dll.so
  [71/133] System.Collections.Immutable.dll -> System.Collections.Immutable.dll.so
  [72/133] System.Collections.NonGeneric.dll -> System.Collections.NonGeneric.dll.so
  [73/133] System.Collections.Specialized.dll -> System.Collections.Specialized.dll.so
  [74/133] System.Collections.dll -> System.Collections.dll.so
  [133/133] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so
  [75/133] System.ComponentModel.Primitives.dll -> System.ComponentModel.Primitives.dll.so
  [76/133] System.ComponentModel.TypeConverter.dll -> System.ComponentModel.TypeConverter.dll.so
  [77/133] System.ComponentModel.dll -> System.ComponentModel.dll.so
  [78/133] System.Console.dll -> System.Console.dll.so
  [79/133] System.Diagnostics.Debug.dll -> System.Diagnostics.Debug.dll.so
  [80/133] System.Diagnostics.DiagnosticSource.dll -> System.Diagnostics.DiagnosticSource.dll.so
  [81/133] System.Diagnostics.Process.dll -> System.Diagnostics.Process.dll.so
  [82/133] System.Diagnostics.Tools.dll -> System.Diagnostics.Tools.dll.so
  [83/133] System.Diagnostics.TraceSource.dll -> System.Diagnostics.TraceSource.dll.so
  [84/133] System.Diagnostics.Tracing.dll -> System.Diagnostics.Tracing.dll.so
  [85/133] System.Drawing.Primitives.dll -> System.Drawing.Primitives.dll.so
  [86/133] System.Drawing.dll -> System.Drawing.dll.so
  [87/133] System.Formats.Asn1.dll -> System.Formats.Asn1.dll.so
  [88/133] System.Globalization.dll -> System.Globalization.dll.so
  [89/133] System.IO.Compression.Brotli.dll -> System.IO.Compression.Brotli.dll.so
  [90/133] System.IO.Compression.dll -> System.IO.Compression.dll.so
  [91/133] System.IO.FileSystem.dll -> System.IO.FileSystem.dll.so
  [92/133] System.IO.Pipelines.dll -> System.IO.Pipelines.dll.so
  [93/133] System.IO.dll -> System.IO.dll.so
  [94/133] System.Linq.Expressions.dll -> System.Linq.Expressions.dll.so
  [95/133] System.Linq.dll -> System.Linq.dll.so
  [96/133] System.Memory.dll -> System.Memory.dll.so
  [97/133] System.Net.Http.dll -> System.Net.Http.dll.so
  [98/133] System.Net.NameResolution.dll -> System.Net.NameResolution.dll.so
  [99/133] System.Net.Primitives.dll -> System.Net.Primitives.dll.so
  [100/133] System.Net.Requests.dll -> System.Net.Requests.dll.so
  [101/133] System.Net.Sockets.dll -> System.Net.Sockets.dll.so
  [102/133] System.Numerics.Vectors.dll -> System.Numerics.Vectors.dll.so
  [103/133] System.ObjectModel.dll -> System.ObjectModel.dll.so
  [104/133] System.Private.Uri.dll -> System.Private.Uri.dll.so
  [105/133] System.Private.Xml.Linq.dll -> System.Private.Xml.Linq.dll.so
  [106/133] System.Private.Xml.dll -> System.Private.Xml.dll.so
  [107/133] System.Reflection.Extensions.dll -> System.Reflection.Extensions.dll.so
  [108/133] System.Reflection.TypeExtensions.dll -> System.Reflection.TypeExtensions.dll.so
  [109/133] System.Reflection.dll -> System.Reflection.dll.so
  [110/133] System.Runtime.Extensions.dll -> System.Runtime.Extensions.dll.so
  [111/133] System.Runtime.InteropServices.RuntimeInformation.dll -> System.Runtime.InteropServices.RuntimeInformation.dll.so
  [112/133] System.Runtime.InteropServices.dll -> System.Runtime.InteropServices.dll.so
  [113/133] System.Runtime.Loader.dll -> System.Runtime.Loader.dll.so
  [114/133] System.Runtime.Numerics.dll -> System.Runtime.Numerics.dll.so
  [115/133] System.Runtime.dll -> System.Runtime.dll.so
  [116/133] System.Security.Cryptography.dll -> System.Security.Cryptography.dll.so
  [117/133] System.Text.Encoding.dll -> System.Text.Encoding.dll.so
  [118/133] System.Text.Encodings.Web.dll -> System.Text.Encodings.Web.dll.so
  [119/133] System.Text.Json.dll -> System.Text.Json.dll.so
  [120/133] System.Text.RegularExpressions.dll -> System.Text.RegularExpressions.dll.so
  [121/133] System.Threading.Tasks.dll -> System.Threading.Tasks.dll.so
  [122/133] System.Threading.Thread.dll -> System.Threading.Thread.dll.so
  [123/133] System.Threading.ThreadPool.dll -> System.Threading.ThreadPool.dll.so
  [124/133] System.Threading.dll -> System.Threading.dll.so
  [125/133] System.Xml.Linq.dll -> System.Xml.Linq.dll.so
  [126/133] System.Xml.ReaderWriter.dll -> System.Xml.ReaderWriter.dll.so
  [127/133] System.Xml.XDocument.dll -> System.Xml.XDocument.dll.so
  [128/133] System.dll -> System.dll.so
  [129/133] netstandard.dll -> netstandard.dll.so
  [130/133] Java.Interop.dll -> Java.Interop.dll.so
  [131/133] Mono.Android.Runtime.dll -> Mono.Android.Runtime.dll.so
  [132/133] Mono.Android.dll -> Mono.Android.dll.so
  [133/133] System.Private.CoreLib.dll -> System.Private.CoreLib.dll.so

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:10:47.93
[11.0.0-prerelease.26230.4+92962e5c46ac08a66ded4c5696209cc60f1a232f] XHarness command issued: android test --app /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk --package-name com.microsoft.maui.controls.devicetests --device-id emulator-5554 -o artifacts/log --timeout 01:00:00 -v --arg TestFilter=Category=CarouselView
�[40m�[37mdbug�[39m�[22m�[49m: ADBRunner using ADB.exe supplied from /home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/tools/net10.0/any/../../../runtimes/any/native/adb/linux/adb
�[40m�[37mdbug�[39m�[22m�[49m: Full resolved path:'/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb'
�[40m�[32minfo�[39m�[22m�[49m: Will attempt to find device supporting architectures: 'arm64-v8a', 'x86_64'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb start-server'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[32minfo�[39m�[22m�[49m: Finding attached devices/emulators...
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb devices -l'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices
�[40m�[37mdbug�[39m�[22m�[49m: Evaluating output line for device serial: emulator-5554          device product:sdk_gphone_x86_64 model:sdk_gphone_x86_64 device:generic_x86_64_arm64 transport_id:2
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell getprop ro.product.cpu.abilist'
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 possible devices. Using 'emulator-5554'
�[40m�[32minfo�[39m�[22m�[49m: Active Android device set to serial 'emulator-5554'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.product.cpu.abi'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop ro.build.version.sdk'
�[40m�[32minfo�[39m�[22m�[49m: Waiting for device to be available (max 5 minutes)
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 wait-for-device'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 -s emulator-5554 shell getprop sys.boot_completed'
�[40m�[37mdbug�[39m�[22m�[49m: sys.boot_completed = '1'
�[40m�[37mdbug�[39m�[22m�[49m: Waited 0 seconds for device boot completion
�[40m�[37mdbug�[39m�[22m�[49m: Working with emulator-5554 (API 30)
�[40m�[37mdbug�[39m�[22m�[49m: Check current adb install and/or package verification settings
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global verifier_verify_adb_installs'
�[40m�[37mdbug�[39m�[22m�[49m: verifier_verify_adb_installs = 0
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 shell settings get global package_verifier_enable'
�[40m�[37mdbug�[39m�[22m�[49m: package_verifier_enable = 
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Installing debug apks on a device might be rejected with INSTALL_FAILED_VERIFICATION_FAILURE. Make sure to set 'package_verifier_enable' to '0'
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[1m�[33mwarn�[39m�[22m�[49m: Hit broken pipe error; Will make one attempt to restart ADB server, and retry the uninstallation
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 kill-server'
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 start-server'
�[40m�[37mdbug�[39m�[22m�[49m: 
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: 20
      Std out:
      
      
      Std err:
      - waiting for device -
      cmd: Can't find service: package
      
      
      
�[40m�[32minfo�[39m�[22m�[49m: Attempting to install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk'
�[41m�[30mfail�[39m�[22m�[49m: Error:
      Exit code: 1
      Std out:
      Serving...
      Performing Incremental Install
      cmd: Can't find service: package
      Performing Streamed Install
      
      
      Std err:
      adb: failed to install /home/vsts/work/1/s/artifacts/bin/Controls.DeviceTests/Release/net10.0-android/com.microsoft.maui.controls.devicetests-Signed.apk: cmd: Can't find service: package
      
      
      
�[41m�[1m�[37mcrit�[39m�[22m�[49m: Install failure: Test command cannot continue
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: 20
      Std out:
      
      
      Std err:
      cmd: Can't find service: package
      
      
      
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: 20
      Std out:
      
      
      Std err:
      cmd: Can't find service: package
      
      
      
XHarness exit code: 78 (PACKAGE_INSTALLATION_FAILURE)
  Tests completed with exit code: 78

🟢 With fix — 📱 CarouselViewTests (VerticalDragOnHorizontalCarouselIsNotIntercepted): PASS ✅ · 231s

(truncated to last 15,000 chars)

923 I DOTNET  : [FILTER] Excluded test (filtered by Trait; 'Category':'Memory'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Modal'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'NavigationPage'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Page'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Path'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Picker'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'RadioButton'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'RefreshView'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'ScrollView'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'SearchBar'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Shape'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Shell'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Slider'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'SwipeView'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TabbedPage'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TextInput'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Toolbar'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TemplatedView'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'View'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'VisualElement'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'VisualElementTree'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'WebView'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Window'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'WindowOverlay'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Xaml'): [Memory] Window Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Accessibility'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Application'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Behavior'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Border'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'BoxView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Button'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'CheckBox'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'CollectionView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Compatibility'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'ContentView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'DatePicker'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Dispatcher'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Editor'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Element'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Entry'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'FlexLayout'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'FlyoutPage'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Frame'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Gesture'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'HybridWebView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Image'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Label'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Layout'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Lifecycle'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'ListView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Map'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'MenuFlyout'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Mapper'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Excluded test (filtered by Trait; 'Category':'Memory'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Modal'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'NavigationPage'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Page'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Path'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Picker'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'RadioButton'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'RefreshView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'ScrollView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'SearchBar'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Shape'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Shell'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Slider'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'SwipeView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TabbedPage'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TextInput'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Toolbar'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'TemplatedView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'View'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'VisualElement'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'VisualElementTree'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'WebView'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.089  5895  5923 I DOTNET  : [FILTER] Included test (filtered by Trait; 'Category':'Window'): [Memory] VisualDiagnosticsOverlay Does Not Leak
      06-26 13:39:30.107  5895  5923 I DOTNET  : [Test environment: 64-bit .NET .NET 10.0 [collection-per-class, non-parallel]]
      06-26 13:39:30.107  5895  5923 I DOTNET  : [Test framework: xUnit.net 2.9.0.0]
      06-26 13:39:30.118  5895  5923 I DOTNET  : Test collection for Microsoft.Maui.DeviceTests.AlertDialogTests
      06-26 13:39:30.205  5895  5923 I DOTNET  : 	[PASS] AlertDialogButtonColorDarkTheme
      06-26 13:39:30.333  5895  5923 I DOTNET  : 	[PASS] AlertDialogButtonColorLightTheme
      06-26 13:39:30.337  5895  5923 I DOTNET  : Microsoft.Maui.DeviceTests.AlertDialogTests 0.0646050 ms
      06-26 13:39:30.338  5895  5923 I DOTNET  : Test collection for Microsoft.Maui.DeviceTests.CarouselViewTests
      06-26 13:39:30.972  5895  5934 I DOTNET  : 	[PASS] HiddenCarouselViewNoCrash
      06-26 13:39:31.309  5895  5940 I DOTNET  : 	[PASS] DisconnectedCarouselViewDoesNotHookCollectionViewChanged
      06-26 13:39:32.584  5895  5946 I DOTNET  : 	[PASS] Vertical Drag On Horizontal CarouselView Is Not Intercepted
      06-26 13:39:34.946  5895  5952 I DOTNET  : 	[PASS] IndicatorView Provides Correct TalkBack Accessibility Description
      06-26 13:39:36.167  5895  5957 I DOTNET  : 	[PASS] Position Initializes Correctly
      06-26 13:39:37.397  5895  5962 I DOTNET  : 	[PASS] Position Initializes Correctly
      06-26 13:39:38.649  5895  5967 I DOTNET  : 	[PASS] Position Initializes Correctly
      06-26 13:39:38.949  5895  5972 I DOTNET  : 	[PASS] CarouselViewDataTemplateSelectorSelectorNoCrash
      06-26 13:39:38.949  5895  5972 I DOTNET  : Microsoft.Maui.DeviceTests.CarouselViewTests 8.5904662 ms
      06-26 13:39:38.996  5895  5923 I DOTNET  : Xml file was written to the provided writer.
      06-26 13:39:38.996  5895  5923 I DOTNET  : Tests run: 573 Passed: 10 Inconclusive: 0 Failed: 0 Ignored: 563
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6n5x7",
        "exitCode": 0,
        "exitCodeName": "SUCCESS",
        "platform": "android",
        "instrumentationExitCode": 0,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.controls.devicetests
XHarness exit code: 0
  Tests completed successfully

📁 Fix files reverted (1 files)
  • src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs

📱 UI Tests — CarouselView,CollectionView

Detected UI test categories: CarouselView,CollectionView

⚠️ Deep UI tests — 1 category (88 tests) could not run: OneTimeSetUp/fixture setup failure on the platform-pool agent — infrastructure, not a PR test failure (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
CarouselView 0/88 (setup failed; 88 marked failed)
⚠️ CarouselView — fixture setup failed for 88 tests

NUnit reported a OneTimeSetUp/fixture setup failure before test bodies ran; the TRX marked each affected test failed.

Multiple setup failure signatures were present; showing the first one. See the TRX artifact for all details.

OneTimeSetUp: System.TimeoutException : CarouselView reverts to displaying first item in collection when collection modified
at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2757
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2784
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 793
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.NavigateToIssue(String issue) in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 54
   at Microsoft.Maui.TestCases.Tests._IssuesUITest.TryToResetTestState() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/_IssuesUITest.cs:line 25
   at Microsoft.Maui.TestCases.Tests.UITest.FixtureSetup() in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 576
   at UITest.Appium.NUnit.UITestBase.OneTimeSetup() in /_/src/TestUtils/src/UITest.NUnit/UITestBase.cs:line 221
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnSta
...

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


📋 Pre-Flight — Context & Validation

Issue: #22507 - [Android] CarouselView behaves strangely when swiping vertically in view
PR: #31790 - [Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views
Platforms Affected: Android
Files Changed: 1 implementation, 1 test

Key Findings

  • Issue #22507 reports horizontal Android CarouselView transitioning items during vertical swipes that should scroll nested content.
  • PR modifies MauiCarouselRecyclerView.OnInterceptTouchEvent to classify gesture axis after ScaledTouchSlop and delegate off-axis gestures.
  • PR adds an Android Controls device test for vertical-dominant drag non-interception.
  • GitHub CLI auth was unavailable; public GitHub API and local branch diff were used for context.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: medium
Errors: 0 | Warnings: 1 | Suggestions: 1

Key code review findings:

  • ⚠ The only passing alternative adds more Android gesture infrastructure than the PR's local state machine for equivalent behavior.
  • ℹ Consider adding an on-axis CarouselView drag test as follow-up coverage.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #31790 Parent-level off-axis gesture classifier using ScaledTouchSlop, direction lock, and delegation latch ✅ PASSED (Gate supplied by caller) src/Controls/src/Core/Handlers/Items/Android/MauiCarouselRecyclerView.cs, src/Controls/tests/DeviceTests/Elements/CarouselView/CarouselViewTests.Android.cs Original PR

🔬 Code Review — Deep Analysis

Code Review — PR #31790

Independent Assessment

What this changes: MauiCarouselRecyclerView.OnInterceptTouchEvent on Android now tracks the initial touch point, waits until movement exceeds ScaledTouchSlop, classifies the gesture's dominant axis, and returns false for off-axis gestures so nested/child content can receive them. It also preserves the explicit-disabled ItemsView path by deferring to the base implementation, which blocks disabled interaction. An Android device test exercises a vertical-dominant drag on a horizontal CarouselView.
Inferred motivation: A horizontal Android CarouselView can intercept vertical gestures intended for nested scrollable content, causing unintended item transitions.

Reconciliation with PR Narrative

Author claims: Fixes #22507 by distinguishing swipe directions and delegating vertical swipes in a horizontal CarouselView to nested content.
Agreement/disagreement: The implementation matches the claimed Android-only bug and preserves prior disabled-state behavior.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Disabled CarouselView could bypass base interception when returning false for delegated/off-axis paths Prior MauiBot/code review thread ✅ Fixed OnInterceptTouchEvent first calls base.OnInterceptTouchEvent(ev) when ItemsView?.IsEnabled == false && !ItemsView.IsExplicitlyEnabled.
Direction should only be evaluated after a minimum threshold jsuarezruiz inline comment ✅ Fixed _touchSlop = ViewConfiguration.Get(context).ScaledTouchSlop; direction locks only after either axis exceeds slop.
No prior unresolved ❌ Error findings found in the latest local diff. Local/public API review ✅ Fixed Current diff includes the disabled guard and slop-gated classifier.

Blast Radius Assessment

  • Runs for all instances: Yes, Android CarouselView touch interception runs for every CarouselView gesture.
  • Startup impact: No, only a per-instance _touchSlop value is initialized in the platform view constructor.
  • Static/shared state: No, all gesture fields are instance fields and reset per gesture.

CI Status

  • Required-check result: undetermined; gh is unauthenticated in this environment.
  • Classification: local gate was provided as passed by the caller; local candidate tests ran against Android Controls device tests where noted.
  • Action taken: confidence capped to medium/low for CI status, but gate result from the prompt is treated as authoritative for this workflow.

Findings

⚠️ Warning — Candidate comparison should prefer simpler local state over heavier gesture infrastructure

The current PR fix is localized and uses Android's documented ScaledTouchSlop. The only passing alternative found in this loop uses GestureDetector, which adds a Java listener object and indirection in a hot touch path for equivalent behavior.

💡 Suggestion — Add an on-axis regression test if desired

The current regression covers off-axis non-interception. A counterpart horizontal-dominant drag test would protect against accidentally disabling CarouselView's own swipe axis.

Failure-Mode Probing

  • Child reaches scroll boundary mid-gesture: current PR latches _delegatingToChild, so the CarouselView does not hijack later move events in the same gesture.
  • Diagonal tie: absDeltaY > absDeltaX means ties are treated as non-vertical, preserving on-axis CarouselView behavior for a horizontal carousel.
  • Missing Down: fields default to zero; unusual platform event ordering could classify from (0,0), but the next Down and Up/Cancel reset state.
  • Explicitly disabled CarouselView: base intercept path is preserved before any early false return.

Verdict: NEEDS_DISCUSSION

Confidence: medium
Summary: The PR fix is sound and is the best candidate found by this loop. CI status could not be queried through authenticated gh, but the caller supplied a passed gate result and local Android device-test candidate runs confirmed the regression behavior for the relevant attempts.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Child-scroll-capability probe: find child under touch and recurse through native descendants checking off-axis scroll capability ❌ FAIL 1 file Fails because the regression item is plain Grid/Label; handler must classify gesture intent independent of native child scrollability.
2 try-fix Android GestureDetector classifier: use Android internal slop/scroll detection and latch _offAxisGesture ✅ PASS 1 file Passes Android CarouselView device tests, but adds Java listener/detector complexity in a hot path and is not demonstrably better than PR fix.
3 try-fix Child-side RequestDisallowInterceptTouchEvent from a custom Carousel item wrapper ❌ FAIL 3 files Requires full dispatch-path semantics/test rewrite; fails the existing gate regression, which probes OnInterceptTouchEvent directly.
PR PR #31790 Parent-level ScaledTouchSlop classifier with direction lock and delegation latch ✅ PASSED (Gate) 2 files Best fit for the verified regression and simpler than passing alternative.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Candidate 1: child-scroll-capability probe.
maui-expert-reviewer 2 Yes Candidate 2: GestureDetector-delegated classifier after Candidate 1 failure.
maui-expert-reviewer 3 Yes Candidate 3: child-side RequestDisallowInterceptTouchEvent after Candidate 2 passed but was not clearly better.
maui-expert-reviewer 4 No Remaining meaningful approaches collapse into variants of parent-level gesture-axis classification; the PR already implements the simplest passing version.

Exhausted: Yes
Selected Fix: PR's fix — Candidate 2 passed but is more complex; Candidates 1 and 3 failed the stable Android regression criteria. The PR fix remains the simplest robust solution that passes the supplied gate.


📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current title is good, but the description overstates that MauiCarouselRecyclerView.OnInterceptTouchEvent simply returns true by default and does not mention the final touch-slop direction lock, gesture delegation latch, disabled-control guard, or Android regression test.

Recommended title

[Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views

Recommended description

### Issue Details:

When a CarouselView is used with nested or child content on Android, swipe gestures are not handled correctly. A horizontal CarouselView can intercept vertical-dominant drag gestures that should be delegated to the child content, causing unwanted CarouselView item transitions instead of allowing the intended vertical interaction.

### Root Cause

`MauiCarouselRecyclerView.OnInterceptTouchEvent` previously deferred swipe-enabled gestures to the base RecyclerView interception behavior without first distinguishing the user's intended gesture axis. As a result, vertical-dominant gestures with enough horizontal movement could still be treated as CarouselView page swipes.

### Description of Change

The Android CarouselView touch interception logic now classifies gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android `ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a horizontal CarouselView, so child/nested content can receive the gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence so the CarouselView does not hijack later move events.
- Preserves disabled CarouselView behavior by deferring to the base interception path when the `ItemsView` is explicitly disabled.

An Android device regression test was added to verify that a vertical-dominant drag on a horizontal CarouselView is not intercepted.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507

### Screenshots
| Before | After |
|--------|-------|
| <video src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67"> | <video src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> |

🏁 Report — Final Recommendation

Comparative Report — PR #31790

Candidate ranking

Rank Candidate Regression result Assessment
1 pr ✅ Passed Best candidate. It fixes the verified Android CarouselView off-axis interception regression with a localized parent-level gesture classifier using ScaledTouchSlop, direction locking, and a delegation latch.
2 pr-plus-reviewer ✅ Passed Equivalent to pr; the expert reviewer produced no actionable inline findings, so no sandbox changes were applied. Ranked below pr only because it does not differ from the submitted fix.
3 try-fix-2 ✅ Passed Functionally valid, but uses Android GestureDetector plus an additional Java listener object in a touch hot path for equivalent behavior. It is more complex than the PR's direct state machine without a corresponding benefit.
4 try-fix-1 ❌ Failed Child-scroll-capability probing does not satisfy the regression because the failing scenario can occur over plain non-scrollable item content. It also lacks the PR's per-gesture delegation latch.
5 try-fix-3 ❌ Failed Child-side RequestDisallowInterceptTouchEvent did not satisfy the stable gate, which directly probes the parent OnInterceptTouchEvent behavior. It would require changing test semantics and is not a drop-in fix.

Comparative analysis

The passing candidates all address gesture-axis classification at the Android CarouselView touch boundary. The raw PR does this with the least machinery: it stores the initial touch point, waits for movement beyond Android touch slop, classifies the dominant axis once, and keeps returning false for the rest of an off-axis gesture. That directly targets the verified failure mode where a vertical-dominant drag on a horizontal CarouselView was intercepted by the carousel.

pr-plus-reviewer adds no changes because expert review returned no actionable inline findings. It is therefore not a distinct improvement over the submitted PR.

try-fix-2 also passes, but it moves the same classification into GestureDetector and a nested listener. That increases object/lifecycle complexity in a touch hot path while producing the same externally verified behavior. The simpler PR state machine is easier to reason about and better aligned with the narrow Android handler fix.

The failed candidates must rank below all passing candidates. try-fix-1 fails because off-axis delegation cannot depend on whether the touched child currently reports native scrollability; the regression test uses plain Grid/Label content. try-fix-3 fails because child-side disallow-intercept logic is bypassed by the parent-level interception path covered by the gate.

Winner

Winner: pr

Rationale: The raw PR fix is the simplest passing implementation, preserves disabled CarouselView behavior, requires no expert-review follow-up changes, and avoids the additional Android GestureDetector infrastructure used by the only passing alternative.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 26, 2026
@kubaflo

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@Dhivya-SF4094 — test-failure review results are available based on commit cd199d8.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Not ready Failures 12 Baseline 5 on base Platform Android

Test Failure Review: Not ready - click to expand

Overall verdict: Not ready

The Android API 30 UI tests (Publish the android_ui_tests_controls_30 test results) show a deterministic regression vs base — 2 legs were green on base build 1483327 but red on this PR, which directly modifies Android CarouselView touch handling and warrants investigation. The remaining 10 failures are unattributed (indeterminate) infrastructure and flakiness issues that cannot be cleared without human review; 11 unexplained build legs and 7 unverified device-test green checks further block the gate.

Coverage: 162 checks · 153 passing · 9 failing · 0 pending · 0 inaccessible · 1 unmapped · 11 unexplained build legs · 0 unaccounted failing checks · 0 aborted failing checks · 0 canceled-build checks · 7 device-test unverified · 10 unattributed · 2 regressed-vs-base. Deterministic ceiling: Not ready — 1 unmapped check (Build Analysis), 11 unexplained build legs, 10 unattributed failures, 7 device-test checks unverified, 2 legs regressed vs base.

Failure Verdict On base? Evidence
Publish the android_ui_tests_controls_30 test results - build error Likely PR-caused no — regressed regressed-vs-base; 2 legs GREEN on base build 1483327, RED on this PR; PR modifies Android CarouselView touch interception directly affecting the API 30 controls suite; "There are one or more test failures detected in result files"
SoftInputExtensionsPageTest Needs human investigation no — regressed (leg; ci-scan demoted) indeterminate; leg-level regressed but ci-scan #36083 (recurring/test) demoted attribution; NullReferenceException; 2 occurrences
VerifyEntryControlWhenPlaceholderTextSet Needs human investigation no — regressed (leg; ci-scan demoted) indeterminate; leg-level regressed but ci-scan #35958 (recurring/leg) demoted attribution; app crash ("app was expected to be running still"); 2 occurrences
CookiesCorrectlyLoadWithMultipleWebViews Needs human investigation also-red indeterminate; ci-scan #36117 (recurring/test); TimeoutException; 6 occurrences; also on base by test (alsoFailsOnBaseline)
WebViewNoCrashPopup Needs human investigation also-red indeterminate; ci-scan #36037 (recurring/leg); app unresponsive/force-terminated ("App.AppState query did not complete"); 4 occurrences; not on base by test
WebViewDoesntCrashWhenLoadingAHeavyPageAndUsingExecutionModeSeparateProcess Needs human investigation also-red indeterminate; ci-scan #36037 (recurring/leg); app unresponsive/force-terminated; 4 occurrences; also on base by test
DeviceTestsWindows (Windows) - build error Needs human investigation also-red indeterminate; PowerShell exited with code 1; Windows device test build error; leg also red on base build
to find package 'platform-tools;35.0.2' Needs human investigation yes indeterminate; ci-scan #35958 (recurring/leg); avdmanager exited with error; also on base by test but leg succeeded-on-base; 2 platform variants (unknown, android)
MemoryLeakB42329 Needs human investigation yes indeterminate; ci-scan #35957 (recurring/test); TimeoutException waiting for element; 2 occurrences; also on base by test, leg inconclusive on base
Publish the ios_ui_tests_mono_controls_latest test results - build error Needs human investigation no indeterminate; "There are one or more test failures detected in result files"; base leg comparison inconclusive
Build Analysis Insufficient data Unmapped check; no inspectable AzDO build evidence; see Arcade landing page

Recommended action

A human reviewer should inspect the Publish the android_ui_tests_controls_30 test results logs to confirm whether the regression is caused by the CarouselView touch interception change, then confirm or dismiss the remaining unattributed failures and verify device-test results before merging.

Evidence details

PR scope: 2 files changed (1 test file); Android platform only; area-controls-collectionview. PR title: [Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views.

Builds inspected:

  • maui-pr-devicetests build 1482483 (PR), base build 1483328 (refs/heads/main, 06/26/2026)
  • maui-pr-uitests build 1482482 (PR), base build 1483327 (refs/heads/main, 06/26/2026); only 8 of 30 failed build logs were inspected — baseline list may be incomplete.

Deterministic regression: Publish the android_ui_tests_controls_30 test results - build error appears on 2 legs of the Controls (API 30) Border/BoxView/Brush/Button job. Both legs show green in base build 1483327 but red on this PR. The PR's changes to Android CarouselView OnInterceptTouchEvent are in the same platform area as the failing API 30 UI test suite.

Unexplained build legs (11): Failed legs that produced no extractable failure — open each leg's log to inspect: Controls ViewBaseTests/VisualStateManager/Window, Controls Page/Performance/Picker/ProgressBar, Controls SearchBar/Shape/Slider, Controls (vlatest) Editor/Effects/Essentials/FlyoutPage/Focus/Fonts/Frame/Gestures/GraphicsView, Publish the mac_ui_tests_controls test results, Controls (vlatest) WebView, Controls Layout, Controls (API 30) Border/BoxView/Brush/Button, Controls ListView, Controls CollectionView.

Unverified device tests (7): All major maui-pr-devicetests green checks (Android CoreCLR, Android Mono, MacCatalyst Mono, Windows build, iOS Mono, ios/catalyst/android Mono build) read green, but Failed == 0 could not be positively confirmed. Helix aggregation returned 404; no authenticated test API was available. XHarness exits 0 even on device test failure.

ci-scan demotions (2): SoftInputExtensionsPageTest and VerifyEntryControlWhenPlaceholderTextSet both show leg-level regression, but ci-scan base-history patterns (#36083, #35958) indicate recurring flakiness; attribution was demoted to indeterminate for both.

AzDO access: Unauthenticated. Build metadata, timelines, and logs queried from public AzDO REST API. Helix test results unavailable (404 response). No authenticated test-run API queries.

@Dhivya-SF4094

Copy link
Copy Markdown
Contributor Author

Verified the CI failed test case; the same tests passes locally.

@kubaflo
kubaflo changed the base branch from main to inflight/current June 30, 2026 14:00
@kubaflo
kubaflo merged commit 5ce1544 into dotnet:inflight/current Jun 30, 2026
153 of 163 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 30, 2026
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
kubaflo pushed a commit that referenced this pull request Jul 22, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
kubaflo pushed a commit that referenced this pull request Jul 28, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
kubaflo pushed a commit that referenced this pull request Jul 29, 2026
…wipes to nested views (#31790)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->

### Issue Details:

When a CarouselView is used with nested scrollable content (e.g.,
CollectionView), swipe gestures are not handled correctly. By default,
the CarouselView intercepts all touch events, including vertical swipes,
even when the intent is to scroll the child view.

### Root Cause
By default, MauiCarouselRecyclerView overrides OnInterceptTouchEvent and
returns true, meaning it intercepts touch gestures before child views
(like CollectionView) can process them.
As a result, Vertical swipes however, are also partially intercepted by
the Carousel, even when the intent is to scroll inside the nested
CollectionView.
This causes a conflict: vertical gestures intended for the inner
CollectionView sometimes trigger an unwanted item transition in the
CarouselView.

### Description of Change
The Android CarouselView touch interception logic now classifies
gestures before allowing the CarouselView to intercept them:

- Records the initial touch position on `MotionEventActions.Down`.
- Waits until movement exceeds Android
`ViewConfiguration.ScaledTouchSlop`.
- Locks the gesture direction once per gesture by comparing horizontal
and vertical movement.
- Returns `false` for off-axis gestures, such as a vertical drag on a
horizontal CarouselView, so child/nested content can receive the
gesture.
- Latches delegated off-axis gestures for the rest of the touch sequence
so the CarouselView does not hijack later move events (for example, when
the child reaches its scroll boundary).
- Preserves disabled CarouselView behavior by deferring to the base
interception path when the `ItemsView` is explicitly disabled.

### Validated the behaviour in the following platforms
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac

### Issues Fixed:
Fixes #22507 

### Screenshots
| Before  | After |
|---------|--------|
|  <video
src="https://github.com/user-attachments/assets/8cda4135-cae8-45a6-9508-33769704ca67">
|   <video
src="https://github.com/user-attachments/assets/34a30017-3640-4865-83ec-cf90a0347412"> 
|
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-controls-collectionview CollectionView, CarouselView, IndicatorView community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] CarouselView behaves strangely when swiping vertically in view

10 participants