-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix CollectionView.Header is header is not scrollable in Android platform #31661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0eb9495
3a1b984
d71ed0f
e73b8a9
0528a5a
daceeaf
9fbff30
7f29508
05a9003
2c1b189
bd4a8d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
| using Android.Content; | ||
| using Android.Views; | ||
| using Microsoft.Maui.Graphics; | ||
| using AndroidX.Core.Widget; | ||
| using AndroidX.RecyclerView.Widget; | ||
| using AView = Android.Views.View; | ||
|
|
||
| namespace Microsoft.Maui.Controls.Handlers.Items | ||
|
|
@@ -37,6 +39,75 @@ internal Func<Size?> RetrieveStaticSize | |
| set => _retrieveStaticSize = new WeakReference(value); | ||
| } | ||
|
|
||
| public override bool DispatchTouchEvent(MotionEvent e) | ||
| { | ||
| if (IsHeaderOrFooterContent()) | ||
| { | ||
|
|
||
| if (e.Action == MotionEventActions.Up || e.Action == MotionEventActions.Cancel) | ||
| { | ||
| Parent?.RequestDisallowInterceptTouchEvent(false); | ||
| } | ||
| } | ||
|
|
||
| return base.DispatchTouchEvent(e); | ||
| } | ||
|
|
||
| public override bool OnInterceptTouchEvent(MotionEvent ev) | ||
| { | ||
| if (IsHeaderOrFooterContent()) | ||
| { | ||
| if (ev.Action == MotionEventActions.Down) | ||
| { | ||
| Parent?.RequestDisallowInterceptTouchEvent(true); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return base.OnInterceptTouchEvent(ev); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines if this ItemContentView is being used for header or footer content | ||
| /// by checking if the contained View is the same as the ItemsView's Header or Footer | ||
| /// </summary> | ||
| bool IsHeaderOrFooterContent() | ||
| { | ||
| if (View == null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Find the parent ItemsView by traversing up the view hierarchy | ||
| var itemsView = FindParentItemsView(); | ||
| if (itemsView is StructuredItemsView structuredItemsView) | ||
| { | ||
| // Check if our View is the same object reference as the header or footer | ||
| return ReferenceEquals(View, structuredItemsView.Header) || | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] CollectionView Android - This only recognizes direct |
||
| ReferenceEquals(View, structuredItemsView.Footer); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Finds the parent StructuredItemsView by traversing the logical parent chain | ||
| /// </summary> | ||
| StructuredItemsView FindParentItemsView() | ||
| { | ||
| var current = View?.Parent; | ||
| while (current != null) | ||
| { | ||
| if (current is StructuredItemsView itemsView) | ||
| { | ||
| return itemsView; | ||
| } | ||
|
|
||
| current = current.Parent; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| internal void RealizeContent(View view, ItemsView itemsView) | ||
| { | ||
| Content = CreateHandler(view, itemsView); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| using System.Collections.ObjectModel; | ||
| using Microsoft.Maui.Controls; | ||
|
|
||
| namespace Maui.Controls.Sample.Issues | ||
| { | ||
| [Issue(IssueTracker.Github, 22120, "CollectionView.Header is not scrollable in Android platform", | ||
| PlatformAffected.Android)] | ||
| public class Issue22120 : ContentPage | ||
| { | ||
| public Issue22120() | ||
| { | ||
| Title = "Issue 22120"; | ||
|
|
||
| // Create header items for the ListView inside ScrollView | ||
| var headerItems = new List<string>(); | ||
| for (int i = 1; i <= 15; i++) | ||
| { | ||
| headerItems.Add($"Header Item {i}"); | ||
| } | ||
|
|
||
| // Create collection items | ||
| var collectionItems = new List<string> | ||
| { | ||
| "Pink", "Green", "Blue", "Yellow", "Orange", "Purple", "SkyBlue", "PaleGreen" | ||
| }; | ||
|
|
||
| // Create the ListView for the header content with ItemTemplate | ||
| var headerListView = new ListView | ||
| { | ||
| AutomationId = "Issue22120HeaderListView", | ||
| HorizontalOptions = LayoutOptions.Center, | ||
| ItemsSource = headerItems, | ||
| HeightRequest = 400, | ||
| ItemTemplate = new DataTemplate(() => | ||
| { | ||
| var label = new Label | ||
| { | ||
| Padding = new Thickness(10), | ||
| BackgroundColor = Colors.LightBlue | ||
| }; | ||
| label.SetBinding(Label.TextProperty, "."); | ||
|
|
||
| return new ViewCell | ||
| { | ||
| View = label | ||
| }; | ||
| }) | ||
| }; | ||
|
|
||
| // Create the ScrollView for the header containing the ListView | ||
| var headerScrollView = new ScrollView | ||
| { | ||
| AutomationId = "Issue22120HeaderScrollView", | ||
| HeightRequest = 200, | ||
| Content = headerListView | ||
| }; | ||
|
|
||
| // Create the main CollectionView | ||
| var collectionView = new CollectionView | ||
| { | ||
| AutomationId = "Issue22120CollectionView", | ||
| Margin = new Thickness(20), | ||
| HorizontalOptions = LayoutOptions.Fill, | ||
| VerticalOptions = LayoutOptions.Fill, | ||
| ItemsLayout = new GridItemsLayout(3, ItemsLayoutOrientation.Vertical), | ||
| Header = headerScrollView, | ||
| ItemsSource = collectionItems, | ||
| ItemTemplate = new DataTemplate(() => | ||
| { | ||
| var button = new Button | ||
| { | ||
| Margin = new Thickness(5), | ||
| Padding = new Thickness(0), | ||
| HeightRequest = 60, | ||
| HorizontalOptions = LayoutOptions.Fill, | ||
| VerticalOptions = LayoutOptions.Center | ||
| }; | ||
| button.SetBinding(Button.TextProperty, "."); | ||
| return button; | ||
| }) | ||
| }; | ||
|
|
||
| Content = collectionView; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| public class Issue22120 : _IssuesUITest | ||
| { | ||
| public Issue22120(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "CollectionView.Header is not scrollable in Android platform"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.CollectionView)] | ||
| public void CollectionViewHeaderScrollViewIsScrollable() | ||
| { | ||
| App.WaitForElement("Issue22120HeaderScrollView"); | ||
| App.WaitForElement("Header Item 1"); | ||
| App.ScrollDown("Issue22120HeaderScrollView", ScrollStrategy.Gesture, swipePercentage: 0.9, swipeSpeed: 1000); | ||
| App.WaitForElement("Header Item 7", timeout: TimeSpan.FromSeconds(5)); | ||
| App.ScrollDown("Issue22120HeaderScrollView", ScrollStrategy.Gesture, swipePercentage: 0.9, swipeSpeed: 1000); | ||
| App.WaitForElement("Header Item 12", timeout: TimeSpan.FromSeconds(5)); | ||
| App.ScrollUp("Issue22120HeaderScrollView", ScrollStrategy.Gesture, swipePercentage: 0.9, swipeSpeed: 1000); | ||
| App.ScrollUp("Issue22120HeaderScrollView", ScrollStrategy.Gesture, swipePercentage: 0.9, swipeSpeed: 1000); | ||
| App.WaitForElement("Header Item 1", timeout: TimeSpan.FromSeconds(5)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[major] Android Platform Specifics - Disallowing parent interception for the entire gesture means a drag that starts in a header/footer scroller cannot hand off to the outer
CollectionViewwhen the nested scroller is already at, or reaches, its top/bottom edge. The user-visible case is a scrollable header at its boundary: the same swipe should continue moving the collection, but the parent remains disallowed untilUp/Cancel. Please scope this to actual scrollable-child handling, or release/re-evaluate once the nested target cannot scroll in the gesture direction.