dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.11k stars 1.73k forks source link

Android Grid.WidthRequest/Grid.HeightRequest mismatches Grid.Measure results #25365

Open LeadAssimilator opened 2 days ago

LeadAssimilator commented 2 days ago

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

  1. Create a new MAUI and XF application (android only)
  2. Replace the content of MainPage with a Grid named "grid":
  3. Set the Grid WidthRequest = 117 and HeightRequest = 117

    
    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MauiRequestMeasure"
             x:Class="MauiRequestMeasure.MainPage">
    
    <Grid
        x:Name="grid"
        WidthRequest="117"
        HeightRequest="117">
    </Grid>

4. Override MainPage.LayoutChildren and call/log grid.Measure(width, height);
```csharp
namespace MauiRequestMeasure
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            var sizeRequest = grid.Measure(width, height);
            Console.WriteLine($"Grid measures to: {sizeRequest}");

            base.LayoutChildren(x, y, width, height);
        }
    }
}
  1. Create a Pixel 7 Pro on api 34 via ADM from Visual Studio with default settings
  2. Deploy and run both apps
  3. Observe the same width/height yielding different measure results:
    • MAUI: Request=117.142857142857x117.142857142857, Minimum=117.142857142857x117.142857142857
    • XF: Request=117x117, Minimum=117x117

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

LeadAssimilator commented 2 days ago

The reproduction steps are the sample.

LeadAssimilator commented 1 day ago

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...