Home About Eric Topics SourceGear

2019-09-16 15:00:00

Xamarin.Forms: StackLayout vs Grid

When using Xamarin.Forms, it's pretty easy to get addicted to StackLayout. And why not? It's very convenient for the developer. Just toss in some child views, and they will get positioned nicely in a line.

<StackLayout Orientation="Vertical">
    <Label Text="One"/> 
    <Label Text="Two"/> 
    <Label Text="Three"/> 
    <Label Text="Four"/> 
    <Label Text="Five"/> 
</StackLayout>

In contrast, the Grid layout is relatively tedious, since we have to specify row and column definitions and then set the Row and Column attributes on each child view.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Label 
        Grid.Row="0" Grid.Column="0" 
        Text="One"
        /> 
    <Label 
        Grid.Row="1" Grid.Column="0" 
        Text="Two"
        /> 
    <Label 
        Grid.Row="2" Grid.Column="0" 
        Text="Three"
        /> 
    <Label 
        Grid.Row="3" Grid.Column="0" 
        Text="Four"
        /> 
    <Label 
        Grid.Row="4" Grid.Column="0" 
        Text="Five"
        /> 
</Grid>

However, the developer convenience of StackLayout comes with a tradeoff. This is a case where knowing how Xamarin.Forms works "under the hood" can help us write better apps.

Measurement

A Xamarin.Forms Layout is responsible for setting the size and position of all its child views. The issue in play for this blog entry is the question of whether the child view is participating in the decision.

In every case, the decision ends with the Layout telling the child, "Here is the box you have to live in. Deal with it." In some cases, that edict is all that happens.

But in other cases, the Layout first asks the child, "What size do you want to be?", and then it uses that information to make a final decision.

In terms of performance, this "measurement" step can be expensive. It is worth avoiding when it is not needed.

So how do we do that? In other words, in the text above, when I said "in some cases" and "in other cases", exactly which cases are which?

Grid, Auto, StackLayout

Here's a mostly-correct answer:

And this is why my fondness for StackLayout is fading fast. It's a convenient abstraction, but it always measures its child views, whether that measurement is necessary or not.

On the other hand, if I do the extra (read: "tedious") work involved in using a Grid, and if I take the time to define the rows and columns such that Auto is not needed, I can eliminate the measurement and get a better performing UI.

Example

Suppose I want a horizontal layout which has a label filling the space between two buttons. With a StackLayout, the corresponding XAML might look like this:

<StackLayout Orientation="Horizontal">
    <Button 
        Text="One"
        />
    <Label 
        Text="Hello" 
        HorizontalOptions="FillAndExpand" 
        HorizontalTextAlignment="Center" 
        />
    <Button 
        Text="Two"
        />
</StackLayout>

The equivalent Grid is:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
 
     <Button 
         Grid.Row="0" Grid.Column="0" 
         Text="One" 
         />
     <Label 
         Grid.Row="0" Grid.Column="1" 
        Text="Hello" 
        HorizontalTextAlignment="Center" 
         />
     <Button 
         Grid.Row="0" Grid.Column="2" 
         Text="Two"
         />
 </Grid>

The Grid certainly does involve more clutter. However, now I can tweak the column definitions to get rid of Auto. For example, if I give the buttons absolute widths, they don't need to be measured during layout anymore:

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="80"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="80"/>
    </Grid.ColumnDefinitions>

As I said, StackLayout is more convenient. There may be cases where it is a bit tricky to express a desired layout with row and column definitions. Don't give up too easily. The layout capabilities of Grid are very powerful.

More correct

Remember earlier when I tagged an answer as "mostly-correct"? Well, it would have been a bit more correct if I had taken the time to explain the following exception to the rule:

Measurement can still happen without GridUnitType.Auto if you use HorizontalOptions or VerticalOptions with a LayoutOptions that has alignment set to anything but Fill.

(The previous paragraph was a mouthful, which is why I didn't want to inline that explanation into the section above.)

It remains true that as long as GridUnitType.Auto is nowhere in sight, measurement is not used to determine the row and column sizes for the grid. The grid calculates the proper sizes for all its rows and columns without asking for advice from the child views. And then, as always, it tells each child view, "Here is the box you have to live in. Deal with it."

But then, when it comes time to "Deal with it", depending on the LayoutOptions, measurement may be needed to determine how the child view is placed into that box:

For example, suppose a grid cell is determined to have a width of 100, and a label therein would say (if asked) that it wants to have a width of 80.

To be fair, in terms of performance, this particular measurement scenario is not as bad as the cases mentioned above.

Still a little bit incorrect

A thorough discussion of Xamarin.Forms layout would be lengthy, and I'm trying to keep this blog post fairly focused. I have intentionally omitted some things.

There are places in my explanations above where I speak of a grid cell in the singular when in fact it could be a span.

Also, I didn't bother discussing the case where the measured size of a child view is larger than the size of grid cell provided by the grid.

And I didn't talk about how WidthRequest and HeightRequest are used with measurement.

Finally, I completely ignored the issue of margins and how they affect the way the child view is placed during "Deal with it".

Summarizing

If you want to avoid measurement in your UI:

Problems

When you ask a child view to measure itself, the work to be done depends on what kind of child view it is.

If it's a label, then something has to figure out the width and height of its text. That involves font metrics, which probably has to happen in platform-specific code.

If that child view is an image, then something has to figure out its width and height. That involves reading (at least part of) the image file and decoding it. And if the image file is actually coming from, say, an HTTP access, now we have a network delay. And as always, make sure that website is using TLS. So, now your simple need for a width involves file I/O, network latency, data compression, and cryptography.

And what if the child view is itself a Layout? Well, that Layout probably can't measure itself without asking its child views for measurements, after which it'll have some arithmetic to do. Now we've got recursion.

So if you're trying to get the slowest possible UI with Xamarin.Forms, follow these guidelines:

That should be fairly effective way to drain a mobile phone battery for no actual benefit.

Caveats

It is worth mentioning that having a child view provide information about its desired size is a feature that has legitimate use cases.

First of all, I acknowledge that there could be situations where the "performance vs developer convenience" tradeoff is worth it.

A better example is the issue of localization. The length of text (on, say, a label) can change a great deal depending on which natural language is being used. It is very common to create a piece of UI sized for a string in English and then discover that the text no longer fits after translation to another language. Allowing the label to request a size is a natural way to deal with that problem.

Bottom line

I am not saying you should never use Xamarin.Forms in ways that need child view measurement. If you actually need StackLayout or GridUnitType.Auto, go ahead and use them.

Rather, I am recommending awareness of the tradeoffs:

For further reading on Xamarin.Forms performance, I suggest:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/deploy-test/performance