[Android] CarouselView: Fix touch interception to delegate vertical swipes to nested views - #31790
Conversation
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/rebase |
0965507 to
28ea8c6
Compare
There was a problem hiding this comment.
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 |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
127bc37 to
b27f6a7
Compare
|
/rebase |
e6a9d53 to
f0ba566
Compare
f0ba566 to
d1286cc
Compare
|
Addressed concerns raised in the AI summary. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
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.
🗂️ 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
🧪 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.OnInterceptTouchEventto classify gesture axis afterScaledTouchSlopand 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
_touchSlopvalue 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;
ghis 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 > absDeltaXmeans 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 nextDownandUp/Cancelreset state. - Explicitly disabled CarouselView: base intercept path is preserved before any early
falsereturn.
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.
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Not ready - click to expandOverall verdict: Not ready The Android API 30 UI tests ( 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.
Recommended actionA human reviewer should inspect the Evidence detailsPR 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:
Deterministic regression: 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 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 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. |
|
Verified the CI failed test case; the same tests passes locally. |
…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"> |
…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"> |
…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"> |
…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"> |
…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"> |
…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"> |
…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"> |
…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"> |
…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"> |
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:
MotionEventActions.Down.ViewConfiguration.ScaledTouchSlop.falsefor off-axis gestures, such as a vertical drag on a horizontal CarouselView, so child/nested content can receive the gesture.ItemsViewis explicitly disabled.Validated the behaviour in the following platforms
Issues Fixed:
Fixes #22507
Screenshots
22507_Android_BeforeFix.mov
22507_Android_AfterFix.mov