Open LeadAssimilator opened 1 month ago
The reproduction steps are the sample.
Also interesting and inconsistent behavior is the measure results of putting certain controls directly in the root of a page versus nesting them in a layout versus in a compatibility layout and when they are measured at different times when having a size request of 117 on this device.
Any layout when added anywhere always measures as 117.142857142857. Any view when added to the root of a ContentPage always measures as 117. Any view when added to a compatibility layout (including ContentView) always measures to 117. Any non-compatibility-layout view (aka not ContentView) when added to a layout always measures as 117.142857142857. Any compatibility layout (including ContentView) when added to a layout measures as 117.142857142857 during the parent layout. Any compatibility layout (including ContentView) when added to a layout measures as 117 during its children layout. Any compatibility layout (including ContentView) when added to another compatibility layout always measures as 117.
If controls are allowed to be larger than their Size Requests in MAUI then is a breaking change from XF that needs documented, and it means the Maximum Size Request isn't actually a maximum either since it is getting exceeded. But then this behavior change also leads to the scenario outlined in the OP where we can be relying on a certain number of controls fitting in a given area which won't all fit once the over-sized amount compounds. And that behavior is also ultimately at odds with how controls measure when contained in ContentPage/TemplatedPage or ContentView/TemplatedView which silently act like or are compatibility layouts.
So something is surely wrong here...
Again, the reproduction steps are the sample. If they are followed, the problem can be reproduced easily. The steps themselves are not long or complicated. It is two snippets of code. Please explain why they are insufficient and why you even ask for them if you aren't going to use them.
It appears that Layout.Measure overrides VisualElement.Measure and instead calls IView.Measure which behaves very differently from VisualElement.Measure's base implementation. This partly explains the strange behavior I noticed above where different measures in different contexts and children layouts passes were giving different results, because I was inadvertently calling different Measures without realizing it.
IView.Measure appears to do a platform measure and is prone to compounding pixel space conversion errors while VisualElement.Measure doesn't, so those errors are avoided. The fact that Layout.Measure calls IView.Measure seems like the underlying bug here as it leads to completely different and unexpected results. Why is it even doing that? If you want IView.Measure behavior, then cast and call it explicitly, no? Clearly I'm missing something here as to why there are two different implementations of Measure and why Layout wants the one (error prone) behavior and everything else (including compatibility Layout) wants the other.
In the Pixel 7 Pro emulator case, the conversion errors are because the device's native resolution is 1440px (width) with a density of 3.5. When WidthRequest or even MaximumWidthRequest is 117dp, it yields an android measure spec of 410px = (int)(float)Math.Ceiling(117dp * 3.5). Because this is an exact measure 410px is returned by the android platform measure, and that gets converted to dp again 117.142857142857dp = 410px / 3.5. So now we have an oversized element, exceeding even the original MaximumWidthRequest by 0.142857142857dp.
If you try to size a layout exactly so it can contain N elements of W size via setting the WidthRequest (or MaximumWidthRequest) of the elements to W and the WidthRequest of the containing layout to N W then things won't actually fit. Take the simple case where N = 2 and W = 117. The layout will measure to exactly 234dp = 117dp 2. Since 234dp is even, no half is added in the to px conversion nor does the conversion back to dp grow. However, the elements of the layout will still measure to 117.142857142857dp and thus take up in total 234.285714285714dp = 2 * 117.142857142857dp which is greater than the layout constrained size of 234dp by 0.285714285714dp.
Depending on the layout, the error can compound and cause cutoff elements, unwanted scrolling or elements to get pushed onto the next row. A good example of this is a FlexLayout of BoxViews. When FlexLayout.WidthRequest = 234, FlexLayout.Wrap = Wrap and BoxView.WidthRequest = 117, one expects 2 elements to fit side by side, but only one does due to the compounding error.
Now if the density scale factor is always a whole or half, then one could say just don't use odd numbers and put a giant warning/note box in the maui docs, but is that really the case?
In general, this whole mess could be worked around by taking the floor of the px -> dp conversion to strip the error added by the ceiling when going dp -> px as long as the density scale factor is at least 1, but is that safe in all cases? Is the error actually wanted somewhere for some reason?
Even with all that, something seems fundamentally awry given the surprising different Layout.Measure vs. VisualElement.Measure implementations and results, which one would expect to actually be the same.
This issue has been verified using Visual Studio 17.12.0 Preview 5 and 17.11.5 (8.0.92 & & 8.0.91 & 8.0.3). Can repro this issue on android platform.
Description
Setting WidthRequest and/or HeightRequest on any Layout control like Grid or StackLayout in MAUI causes it to sometimes Measure slightly larger than the requested size on Android. Even setting MaximumWidthRequest and MaximumHeightRequest yields the same slightly oversized measure for some values. XF however always measures to the requested values.
While the oversized amount is always less than 1, that error can compound in various cases, especially with complex custom layouts containing many sub layouts which end up causing elements that were expected to fit in a given space to be forced into another location. For example, take a layout with a WidthRequest LWR that has N children that are grids with WidthRequest IWR such that LWR = K*(IWR+S)-S where S is some inter-item spacing and K is the number of elements that should fit in the given width LWR. In XF, K items will always fit, but in MAUI sometimes K will fit and sometimes only K-1 will fit due to the measure error.
Steps to Reproduce
Override MainPage.LayoutChildren and call/log layout.Measure(width, height);
Link to public reproduction project repository
No response
Version with bug
8.0.92 SR9.2
Is this a regression from previous behavior?
Yes, this used to work in Xamarin.Forms
Last version that worked well
No response
Affected platforms
Android
Affected platform versions
Android 34 and up on Pixel 7 Pro emulator
Did you find any workaround?
No response
Relevant log output
No response