Skip to content

Commit df57e97

Browse files
BagavathiPerumalPureWeen
authored andcommitted
[Android] Fix for CollectionView.EmptyView does not remeasure its height when the parent layout changes dynamically, causing incorrect sizing. (#33559)
<!-- 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. !!!!!!! --> ### Root Cause: The issue occurs because of Android RecyclerView reusing ViewHolders with previously measured dimensions and not automatically remeasuring them when only the layout size changes, causing the EmptyView to retain its old dimensions instead of adapting to the new available space. ### Fix Description: The fix involves explicitly forcing the EmptyView to remeasure when the RecyclerView dimensions change by detecting width or height updates, locating the corresponding EmptyView ViewHolder, and requesting a layout pass so it is resized to match the new available space. When the ViewHolder isn't immediately available, the layout request is deferred to the next UI loop iteration. ### Issues Fixed Fixes #33324 ### Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Screenshot Before Issue Fix | After Issue Fix | |----------|----------| |<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/fcdb9637-56b8-4dd3-8a23-1dc4e881bb36">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/3ff0bee9-8c80-4d3f-8042-acff1807ece6">|
1 parent 9ba54bf commit df57e97

File tree

5 files changed

+222
-0
lines changed

5 files changed

+222
-0
lines changed

src/Controls/src/Core/Handlers/Items/ItemsViewHandler.Android.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,37 @@ void UpdateEmptyViewSize(double width, double height)
109109

110110
if (adapter is EmptyViewAdapter emptyViewAdapter)
111111
{
112+
double tolerance = 0.001;
113+
var widthChanged = Math.Abs(emptyViewAdapter.RecyclerViewWidth - width) > tolerance;
114+
var heightChanged = Math.Abs(emptyViewAdapter.RecyclerViewHeight - height) > tolerance;
115+
112116
emptyViewAdapter.RecyclerViewWidth = width;
113117
emptyViewAdapter.RecyclerViewHeight = height;
118+
119+
if (widthChanged || heightChanged)
120+
{
121+
// EmptyView position depends on whether the CollectionView has a header
122+
var structuredItemsView = VirtualView as StructuredItemsView;
123+
var hasHeader = (structuredItemsView?.Header ?? structuredItemsView?.HeaderTemplate) is not null;
124+
var emptyViewPosition = hasHeader ? 1 : 0;
125+
126+
// Check if ViewHolder exists and is ready for immediate layout request
127+
var viewHolder = PlatformView.FindViewHolderForAdapterPosition(emptyViewPosition);
128+
if (viewHolder is not null)
129+
{
130+
// ViewHolder exists, request layout immediately to avoid frame delay
131+
viewHolder.ItemView.RequestLayout();
132+
}
133+
else
134+
{
135+
// ViewHolder not created yet, defer layout request to next UI loop iteration
136+
PlatformView.Post(() =>
137+
{
138+
var vh = PlatformView.FindViewHolderForAdapterPosition(emptyViewPosition);
139+
vh?.ItemView.RequestLayout();
140+
});
141+
}
142+
}
114143
}
115144
}
116145
}
50.9 KB
Loading
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System.Collections.ObjectModel;
2+
using System.ComponentModel;
3+
using System.Runtime.CompilerServices;
4+
5+
namespace Maui.Controls.Sample.Issues;
6+
7+
[Issue(IssueTracker.Github, 33324, "CollectionView.EmptyView does not remeasure its height when the parent layout changes dynamically", PlatformAffected.Android)]
8+
public class Issue33324 : ContentPage
9+
{
10+
readonly Issue33324ViewModel _viewModel;
11+
12+
public Issue33324()
13+
{
14+
Title = "Issue 33324 - EmptyView Height";
15+
16+
_viewModel = new Issue33324ViewModel();
17+
BindingContext = _viewModel;
18+
19+
var firstCollectionView = new CollectionView2
20+
{
21+
AutomationId = "FirstCollectionView",
22+
HeightRequest = 380,
23+
ItemTemplate = new DataTemplate(() =>
24+
{
25+
var label = new Label { Margin = new Thickness(10, 5) };
26+
label.SetBinding(Label.TextProperty, ".");
27+
return label;
28+
})
29+
};
30+
firstCollectionView.SetBinding(CollectionView.ItemsSourceProperty, nameof(Issue33324ViewModel.MyItems));
31+
32+
var itemsContainer = new VerticalStackLayout();
33+
itemsContainer.SetBinding(VisualElement.IsVisibleProperty, nameof(Issue33324ViewModel.HasItems));
34+
itemsContainer.Children.Add(new Label
35+
{
36+
Text = "Items Loaded:",
37+
FontAttributes = FontAttributes.Bold,
38+
Margin = new Thickness(10, 0, 0, 0)
39+
});
40+
itemsContainer.Children.Add(firstCollectionView);
41+
42+
var loadButton = new Button
43+
{
44+
Text = "Load Items",
45+
AutomationId = "LoadItemsButton",
46+
Margin = new Thickness(10)
47+
};
48+
loadButton.SetBinding(Button.CommandProperty, nameof(Issue33324ViewModel.LoadItemsCommand));
49+
50+
var topContainer = new VerticalStackLayout();
51+
topContainer.Children.Add(itemsContainer);
52+
topContainer.Children.Add(loadButton);
53+
54+
var emptyViewLabel = new Label
55+
{
56+
Text = "No players available",
57+
AutomationId = "EmptyViewLabel",
58+
HorizontalOptions = LayoutOptions.Center,
59+
VerticalOptions = LayoutOptions.Center,
60+
FontSize = 18,
61+
TextColor = Colors.Black
62+
};
63+
64+
var emptyViewGrid = new Grid
65+
{
66+
AutomationId = "EmptyViewGrid"
67+
};
68+
emptyViewGrid.Children.Add(new BoxView
69+
{
70+
Color = Colors.Pink,
71+
AutomationId = "EmptyViewBackground"
72+
});
73+
emptyViewGrid.Children.Add(emptyViewLabel);
74+
75+
var secondCollectionView = new CollectionView2
76+
{
77+
AutomationId = "SecondCollectionView",
78+
EmptyView = emptyViewGrid
79+
};
80+
secondCollectionView.SetBinding(CollectionView.ItemsSourceProperty, nameof(Issue33324ViewModel.Players));
81+
82+
var mainGrid = new Grid
83+
{
84+
RowDefinitions =
85+
{
86+
new RowDefinition { Height = GridLength.Auto },
87+
new RowDefinition { Height = GridLength.Star }
88+
},
89+
RowSpacing = 20
90+
};
91+
92+
mainGrid.Add(topContainer, 0, 0);
93+
mainGrid.Add(secondCollectionView, 0, 1);
94+
95+
Content = mainGrid;
96+
}
97+
}
98+
99+
public class Issue33324ViewModel : INotifyPropertyChanged
100+
{
101+
bool _hasItems;
102+
ObservableCollection<string> _myItems;
103+
ObservableCollection<string> _players;
104+
105+
public Issue33324ViewModel()
106+
{
107+
_myItems = new ObservableCollection<string>();
108+
_players = new ObservableCollection<string>();
109+
_hasItems = false;
110+
111+
LoadItemsCommand = new Command(LoadItems);
112+
}
113+
114+
public bool HasItems
115+
{
116+
get => _hasItems;
117+
set
118+
{
119+
if (_hasItems != value)
120+
{
121+
_hasItems = value;
122+
OnPropertyChanged();
123+
}
124+
}
125+
}
126+
127+
public ObservableCollection<string> MyItems
128+
{
129+
get => _myItems;
130+
set
131+
{
132+
if (_myItems != value)
133+
{
134+
_myItems = value;
135+
OnPropertyChanged();
136+
}
137+
}
138+
}
139+
140+
public ObservableCollection<string> Players
141+
{
142+
get => _players;
143+
set
144+
{
145+
if (_players != value)
146+
{
147+
_players = value;
148+
OnPropertyChanged();
149+
}
150+
}
151+
}
152+
153+
public Command LoadItemsCommand { get; }
154+
155+
void LoadItems()
156+
{
157+
MyItems.Clear();
158+
for (int i = 1; i <= 10; i++)
159+
{
160+
MyItems.Add($"Item {i}");
161+
}
162+
HasItems = true;
163+
}
164+
165+
public event PropertyChangedEventHandler PropertyChanged;
166+
167+
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
168+
{
169+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
170+
}
171+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using NUnit.Framework;
2+
using UITest.Appium;
3+
using UITest.Core;
4+
5+
namespace Microsoft.Maui.TestCases.Tests.Issues;
6+
7+
public class Issue33324 : _IssuesUITest
8+
{
9+
public override string Issue => "CollectionView.EmptyView does not remeasure its height when the parent layout changes dynamically";
10+
11+
public Issue33324(TestDevice device) : base(device) { }
12+
13+
[Test]
14+
[Category(UITestCategories.CollectionView)]
15+
public void EmptyViewShouldRemeasureWhenParentLayoutChanges()
16+
{
17+
App.WaitForElement("LoadItemsButton");
18+
App.Tap("LoadItemsButton");
19+
App.WaitForElement("SecondCollectionView");
20+
VerifyScreenshot();
21+
}
22+
}
58.2 KB
Loading

0 commit comments

Comments
 (0)