From 45502d6b624e96f41b24764b50515fe57a966d92 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 7 May 2024 10:50:27 -0500 Subject: [PATCH 1/6] Trigger remeasure when constraints on CV changes --- .../Adapters/StructuredItemsViewAdapter.cs | 7 ++- .../Handlers/Items/Android/ItemContentView.cs | 61 +++++++++++++------ .../tests/UITests/Tests/Issues/Issue21967.cs | 29 +++++++++ 3 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 src/Controls/tests/UITests/Tests/Issues/Issue21967.cs diff --git a/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs b/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs index fbf79a88570b..7039497e8d2b 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/Adapters/StructuredItemsViewAdapter.cs @@ -14,9 +14,14 @@ public class StructuredItemsViewAdapter : ItemsVie { Size? _size; + // I'm storing this here because in the children I'm using a weakreference and + // I don't want this action to get GC'd + Action _reportMeasure; + protected internal StructuredItemsViewAdapter(TItemsView itemsView, Func createItemContentView = null) : base(itemsView, createItemContentView) { + _reportMeasure = SetStaticSize; UpdateHasHeader(); UpdateHasFooter(); } @@ -98,7 +103,7 @@ protected override void BindTemplatedItemViewHolder(TemplatedItemViewHolder temp { if (ItemsView.ItemSizingStrategy == ItemSizingStrategy.MeasureFirstItem) { - templatedItemViewHolder.Bind(context, ItemsView, SetStaticSize, _size); + templatedItemViewHolder.Bind(context, ItemsView, _reportMeasure, _size); } else { diff --git a/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs b/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs index af2a7942e70c..f1551faa69fa 100644 --- a/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs +++ b/src/Controls/src/Core/Handlers/Items/Android/ItemContentView.cs @@ -9,8 +9,15 @@ namespace Microsoft.Maui.Controls.Handlers.Items { public class ItemContentView : ViewGroup { - Size? _size; - Action _reportMeasure; + Size? _pixelSize; + WeakReference _reportMeasure; + int _previousPixelWidth; + int _previousPixelHeight; + + Action ReportMeasure + { + get => _reportMeasure?.Target as Action; + } protected IPlatformViewHandler Content; internal IView View => Content?.VirtualView; @@ -53,13 +60,13 @@ internal void Recycle() } Content = null; - _size = null; + _pixelSize = null; } internal void HandleItemSizingStrategy(Action reportMeasure, Size? size) { - _reportMeasure = reportMeasure; - _size = size; + _reportMeasure = new WeakReference(reportMeasure); + _pixelSize = size; } protected override void OnLayout(bool changed, int l, int t, int r, int b) @@ -83,39 +90,54 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) return; } + int pixelWidth = MeasureSpec.GetSize(widthMeasureSpec); + int pixelHeight = MeasureSpec.GetSize(heightMeasureSpec); + var widthMode = MeasureSpec.GetMode(widthMeasureSpec); + var heightMode = MeasureSpec.GetMode(heightMeasureSpec); + + // If the measure changes significantly, we need to invalidate the pixel size + // This will happen if the user rotates the device or even just changes the height/width + // on the CollectionView itself. + if (pixelWidth != 0 && _previousPixelWidth != pixelWidth) + { + _pixelSize = null; + _previousPixelWidth = pixelWidth; + } + + if (pixelHeight != 0 && _previousPixelHeight != pixelHeight) + { + _pixelSize = null; + _previousPixelHeight = pixelHeight; + } + // If we're using ItemSizingStrategy.MeasureFirstItem and now we have a set size, use that // Even though we already know the size we still need to pass the measure through to the children. - if (_size is not null) + if (_pixelSize is not null) { - var w = (int)_size.Value.Width; - var h = (int)_size.Value.Height; + var w = (int)this.FromPixels(_pixelSize.Value.Width); + var h = (int)this.FromPixels(_pixelSize.Value.Height); // If the platform childs measure has been invalidated, it's going to still want to // participate in the measure lifecycle in order to update its internal // book keeping. - _ = View.Measure - ( + _ = (View.Handler as IPlatformViewHandler)?.MeasureVirtualView( MeasureSpec.MakeMeasureSpec(w, MeasureSpecMode.Exactly), MeasureSpec.MakeMeasureSpec(h, MeasureSpecMode.Exactly) ); - SetMeasuredDimension(w, h); + SetMeasuredDimension((int)_pixelSize.Value.Width, (int)_pixelSize.Value.Height); return; } - int pixelWidth = MeasureSpec.GetSize(widthMeasureSpec); - int pixelHeight = MeasureSpec.GetSize(heightMeasureSpec); - - var widthSpec = MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.Unspecified + var width = widthMode == MeasureSpecMode.Unspecified ? double.PositiveInfinity : this.FromPixels(pixelWidth); - var heightSpec = MeasureSpec.GetMode(heightMeasureSpec) == MeasureSpecMode.Unspecified + var height = heightMode == MeasureSpecMode.Unspecified ? double.PositiveInfinity : this.FromPixels(pixelHeight); - - var measure = View.Measure(widthSpec, heightSpec); + var measure = View.Measure(width, height); if (pixelWidth == 0) { @@ -127,8 +149,7 @@ protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) pixelHeight = (int)this.ToPixels(measure.Height); } - _reportMeasure?.Invoke(new Size(pixelWidth, pixelHeight)); - _reportMeasure = null; // Make sure we only report back the measure once + ReportMeasure?.Invoke(new Size(pixelWidth, pixelHeight)); SetMeasuredDimension(pixelWidth, pixelHeight); } diff --git a/src/Controls/tests/UITests/Tests/Issues/Issue21967.cs b/src/Controls/tests/UITests/Tests/Issues/Issue21967.cs new file mode 100644 index 000000000000..209d6ba72b1b --- /dev/null +++ b/src/Controls/tests/UITests/Tests/Issues/Issue21967.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.AppiumTests.Issues +{ + public class Issue21967 : _IssuesUITest + { + public Issue21967(TestDevice device) : base(device) + { + } + + public override string Issue => "CollectionView causes invalid measurements on resize"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void CollectionViewItemsResizeWhenContraintsOnCollectionViewChange() + { + var largestSize = App.WaitForElement("Item1").GetRect(); + App.Tap("Resize"); + var mediumSize = App.WaitForElement("Item1").GetRect(); + App.Tap("Resize"); + var smallSize = App.WaitForElement("Item1").GetRect(); + + Assert.Greater(largestSize.Width, mediumSize.Width); + Assert.Greater(mediumSize.Width, smallSize.Width); + } + } +} From 13d7f843d949ea8b63e60290e6112402e97a901d Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 7 May 2024 11:18:44 -0500 Subject: [PATCH 2/6] - add tests --- .../Issues/Issue21967.xaml | 50 +++++++++++++++++++ .../Issues/Issue21967.xaml.cs | 27 ++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/Controls/samples/Controls.Sample.UITests/Issues/Issue21967.xaml create mode 100644 src/Controls/samples/Controls.Sample.UITests/Issues/Issue21967.xaml.cs diff --git a/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21967.xaml b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21967.xaml new file mode 100644 index 000000000000..d5f0a241b58a --- /dev/null +++ b/src/Controls/samples/Controls.Sample.UITests/Issues/Issue21967.xaml @@ -0,0 +1,50 @@ + + + + + + +