Xamarin apps run on a variety of device sizes - from small phones to bigger tablets - and the devices can change from portrait to landscape mode in an instant. The app UI needs to adapt to these size and orientation changes, look good, and be functional. Every UI framework has the notion of positioning and sizing elements on the screen. As systems have evolved, these layout tools have advanced too, adding features that simplify commonplace layout problems. It's these layouts that provide the means to building adaptive user interfaces. In this article, I'll look at the Xamarin.Forms layout views and show how to use them to build various UI design structures.

Views and Layouts

The visual elements in Xamarin.Forms fall into two separate categories: controls and arrangers. Both are considered visual elements, share a common base class (View), and are collectively known as Views. But their missions are different. View classes (the controls) are responsible for gathering user input or showing information to the user. They are called controls or widgets in other UI frameworks.

Layouts (the arrangers) serve a different purpose. A layout class is a View class that contains a collection of child views. It's responsible for arranging the child views into position in the UI. It also collaborates with each child to determine the final rendering size. To be clear, layouts are also views, but are rarely called that by the community.

Figure 1 shows a list of classes that derive from the View class. Most of them fall into the input/output control category. The classes outlined with red rectangle in Figure 1 are the base classes for the layout views.

Figure 1: The View classes in Xamarin.Forms
Figure 1: The View classes in Xamarin.Forms

Layout Classes

The layout classes do the work of arranging the elements on the screen. Each layout specializes in a style of arrangement. If you're familiar with other XAML frameworks, many of these layout classes (shown in Figure 2) will seem familiar, albeit with different names.

Figure 2: The layout classes in Xamarin.Forms
Figure 2: The layout classes in Xamarin.Forms

The simplest layout containers are the ContentView, Frame, and ScrollView. They're designed to provide a few simple services to the child items. Although they can contain multiple child items, there's no good reason to have more than one child in these layouts. They simply don't offer layout options that help arrange multiple child items. The other layouts (AbsoluteLayout, Grid, RelativeLayout, and StackLayout) excel at arranging multiple child items. It's common to mix and combine multiple layouts into the page UI. Therefore, understanding the strengths of each layout is essential for crafting your pages.

A Word About Color

The Xamarin.Forms prebuilt color palette is small. There are nineteen color constants available. For this article, I want to use a custom set of colors; here's how to make that happen. Start by creating a static class and add some static properties:

namespace Common {
    static class Hues {
        public static readonly Color Sky = Color.FromHex("#226765");
        public static readonly Color Fire = Color.FromHex("#AA4B40");
    }
}

In this example, I used the Color.FromHex method to define the two custom colors. The Color struct has other methods to describe colors (FromRGB, FromHlsa, and FromUint); use whichever method you prefer to define your color set.

To use these custom colors in a page, add a custom XML namespace to the root element:

<ContentPage
    xmlns="..."
    xmlns:x="..."
    xmlns:c="clr-namespace:Common;assembly=Demo" >

I used the prefix c for my XML namespace. Normally, I'd use a more readable name, like common, but I chose the shorter name so that the example fits into the code snippet size constraint for the magazine. Notice the use of the clr-namespace: syntax in the namespace declaration: That's how you indicate which .NET namespace to use.

This snippet uses the Common namespace in the Demo assembly. Look at the code snippet shown earlier and you can see that the color properties are inside the Common.Hues class. Now that the c namespace is registered, I can access the colors with this snippet:

BackgroundColor="{x:Static c:Hues.Sky}"

The ContentView and Frame

ContentView is not intended to be used directly in your XAML tree. Rather, it's designed to be a base class for building your own composite views. The Frame, which derives from ContentView, is a better choice for a wrapper around a child view. Its biggest asset is that it can render a visible border around the child. This next snippet shows how to set the OutlineColor and add a dropshadow to the Frame:

<Frame OutlineColor="White" HasShadow="True">
    <Label Text="The Label" />
</Frame>

The Frame inherits a useful property, Padding, from the Layout base class. Non-layout views don't have margin or padding properties. The only way to add space around a view is it to put it inside a layout and set the Padding property like this:

<Frame Padding="30">
    <Label Text="The Label" />
</Frame>

Because the Padding property is part of the Layout base class, it's settable on any of the other layout classes.

VerticalOptions and HorizontalOptions

Every view has a size that's set by a collaboration between the child view and its layout container. Use the VerticalOptions, HorizontalOptions, WidthRequest, and HeightRequest properties to influence the final rendered size. Let's look at the default size of an element.

<Frame
    Padding="10,20"
    BackgroundColor="{x:Static c:Hues.Sky}">
    <BoxView
        Color="{x:Static c:Hues.Fire}"/>
</Frame>

The example in the code snippet uses a Frame as the layout and a BoxView as the child. The BoxView is a simple view, useful for drawing solid color rectangles. I'm not specifying a width or height for the BoxView. Nor am I setting any value for the VerticalOptions and HorizontalOptions. Figure 3 shows the UI running on an Android emulator.

Figure 3: Default Size for BoxView on Android
Figure 3: Default Size for BoxView on Android

By default, the BoxView is sized to match its container, the Frame. The blue buffer shown around the edge of the BoxView is due to the Padding = "10,20" property on the Frame. In this case, that means 10 pixels of padding on the left and right and 20 pixels of padding on the top and bottom.

The Dynamics of Sizing

Sizing and positioning can get complicated, especially in sophisticated multilayered interfaces. Here are some pointers that will help understand the way the system works.

Each view has a desired size, the size that it wants to be rendered at, if possible. The desired size is calculated internally within the child view. Take a Label as an example. Its desired width depends on many factors. How many characters are in the Text property? What is the font size?

It's the same with the desired height. The desired height will be taller when there are lots of characters, a big font size, and word wrap are enabled. In general, it's better to let the view decide on its own size. You can overrule the desired size if necessary by setting the WidthRequest and HeightRequest property on the child view.

There will be times when the requested size (or desired size) is smaller than the layout container. For example, a 180-pixel width Label within a 300-pixel width StackLayout. In this scenario, the panel defers to the VerticalOptions and HorizontalOptions properties to determine where to place the child element within the extra space. There are several settings available for the LayoutOptions enumeration. The discussion here is around the HorizontalOptions and it's similar for the VerticalOptions property.

  • Start
  • Center
  • End
  • Fill (Default)
  • StartAndExpand
  • CenterAndExpand
  • EndAndExpand
  • FillAndExpand

I'll look at the top four items in this list. LayoutOptions.Start sets the child width to the requested size and then positions the element to the side of the container. In left-to-right languages, the start position is the left side of the container, in right-to-left languages, the start position is on the right side. LayoutOptions.End does the opposite, positioning the element on the right side in left-to-right languages and on the left side in right-to-left languages. LayoutOptions.Center centers the element and keeps the requested size. LayoutOptions.Fill stretches the width of the child element to match the parent container. LayoutOptions.Fill is the default LayoutOption setting.

Take a look at this next snippet. The BoxView doesn't specify a size, so it'll get the default size. It also doesn't indicate the VerticalOptions or HorizontalOptions either, so it defaults to the LayoutOptions.Fill value. As a result of these settings, the parent Frame stretches the BoxView to fit horizontally and vertically.

<Frame Padding="30" BackgroundColor="#226765">
    <BoxView BackgroundColor="#AA4B40"  />
</Frame>

To override LayoutOptions, use this this XAML:

<Frame Padding="30" BackgroundColor="#226765">
    <BoxView BackgroundColor="#AA4B40"
             HorizontalOptions="End"
             VerticalOptions="Start"  />
</Frame>

Now the BoxView is positioned to the upper-left position on my phone (because English is a left-to-right language). The BoxView isn't stretched inside the Frame, so it will fall back to its default size of 40x40-pixels.

ScrollView

ScrollView is useful when the child content is bigger than can fit in the UI. For example, when the text content on a page is too tall to fit on the screen, wrap the Label in a ScrollView. This can make your UI readable on smaller screen sizes. Content that fits on a big-screen phone like the Nexus 6 will still be readable on the smaller Moto G phone. This code snippet shows a tall BoxView within the ScrollView:

<ScrollView
    Padding="30"
    BackgroundColor="{x:Static c:Hues.Sky}"  >
    <BoxView
        Color="{x:Static c:Hues.Fire}"
        HeightRequest="1400" />
</ScrollView>

Figure 4 shows the result. If you look closely at the Android and Windows Phone screenshots, you can see the scrollbar on the right side of the screen. It's not visible on the iPhone screenshot, but that's due to the limitations of my screen capture software.

Figure 4: ScrollView in action
Figure 4: ScrollView in action

StackLayout

This layout is one of my favorites, and I use it everywhere in my UI. It has a simple mission: position the child elements in a stack, either horizontally or vertically. The order that the child elements are added to the layout is the order in which they are stacked. This snippet adds two BoxViews with default HorizontalOptions, and three more with specific HorizontalOptions set:

<StackLayout >
    <!-- defaults to HorizontalOptions="Fill"-->
    <BoxView Color="{x:Static c:Hues.Fire}"  />
    <BoxView Color="{x:Static c:Hues.Sky}" />

    <BoxView Color="{x:Static c:Hues.Sky}"
             HorizontalOptions="Start"/>
    <BoxView Color="{x:Static c:Hues.Fire}"
             HorizontalOptions="Center"/>
    <BoxView Color="{x:Static c:Hues.Sky}"
             HorizontalOptions="End"/>
</StackLayout>

Figure 5 shows the results.

Figure 5: StackLayout on the devices
Figure 5: StackLayout on the devices

Each item is positioned immediately below the previous one, with six pixels spacing between the items. The spacing is adjustable with the Spacing property.

<StackLayout Spacing="18">

Figure 6 is an example of a StackLayout with a horizontal orientation:

<StackLayout Orientation="Horizontal">
    <Image Source="twitter.png"/>
    <Image Source="facebook.png"/>
    <Image Source="pinterest.png"/>
</StackLayout>
Figure 6: Horizontal layout of images
Figure 6: Horizontal layout of images

Two-Dimensional Layout with the Grid

The grid is a time-tested way to lay out information in a two-dimensional space. Designers determine placement of important elements with gridlines. As UI design has formalized, the grid has triumphed as a prime tool in the layout workflow.

The Xamarin Grid is similar to other XAML-based grids, although it has one feature that you haven't seen before. It sports the ColumnSpacing and RowSpacing attributes, which define how much room to put between the rows and columns in the grid.

<Grid ColumnSpacing="6" 
      RowSpacing="4">

Other than this small change, the other Grid attributes are familiar territory for XAML devs. For those new to XAML, here's the overview. You define columns within a column definition section and rows in a row definition section:

<Grid ColumnSpacing="6"
      RowSpacing="4"
      BackgroundColor="{x:Static c:Hues.Sky}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="3*"/>
        <RowDefinition Height="3*"/>
    </Grid.RowDefinitions>
    <!-- child elements go here. -->
</Grid>

This snippet creates a two-row, two-column grid. To control the size of the row or column, use the sizing option specified with the GridUnitType enumeration. There are three choices for GridUnitType sizes: Absolute, Auto, and Star.

An Absolute-size column's width is hard-coded to the specified value. This example shows two columns with fixed widths:

<Grid.ColumnDefinitions>
    <!-- Absolute size-->
    <ColumnDefinition Width="113"/>
    <ColumnDefinition Width="37"/>
</Grid.ColumnDefinitions>

An Auto-sized column's width is determined by its content. An Auto-sized column with a 120-pixel-wide label and an 80-pixel-wide button will be set to 120 pixels. This example shows two columns with automatically sized widths:

<Grid.ColumnDefinitions>
    <!-- Auto size-->
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>

A Star sized column (also known as a proportional size) width is determined by its relation to the other columns in the grid. Take a look at this example:

<Grid WidthRequest="450">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="113"/>
        <ColumnDefinition Width="37"/>
        <ColumnDefinition Width="2*"/>
        <ColumnDefinition Width="3*"/>
        <ColumnDefinition Width="5*"/>
    </Grid.ColumnDefinitions>
</Grid>

To determine the sizes of each column, the Grid starts by resolving the Absolute-size and Auto-size columns. In this case, the two Absolute-sized columns total 150 pixels. The Grid has requested 450-pixels width. Assuming that the Grid is rendered at 450-pixels, that leaves 300-pixels for the remaining three columns. Next, the grid tallies a sum of all the star-sized items (2* + 3* + 5*=10*) and divides each column by the column star value. The results for the third, fourth, and fifth columns are (2/10 (20%), 3/10 (30%), and 5/10 (50%)). Using these percentages, the final rendered sizes for the columns are: 113, 37, 60, 90, and 150.

Even though I'm not showing examples of RowDefinitions, they work the same way for adjusting row heights.

Once the grid is defined, it's time to add the child elements. Unless otherwise indicated, a child is positioned in column 0, row 0. To modify the default placement, use the Grid.Column and Grid.Row attributes.

<BoxView Grid.Row="1" Grid.Column="2"/>

You can also use the Grid.ColumnSpan and Grid.RowSpan attributes to specify how many columns and rows the child view should span.

<BoxView Grid.RowSpan="2"
         Grid.ColumnSpan="4" />

AbsoluteLayout

Use the AbsoluteLayout when you need precise control over the x and y positions, and the width and height of the child elements. It has its place in the layout world. In most situations, it's better to use one of the other layouts; they make for a more responsive design. Absolute layout harkens back to the old days when we brute-forced screen elements into position. Although that's less common now, it does have its place for certain layout challenges.

Each child element describes its location and size with the AbsoluteLayout.LayoutBounds attribute. Use a comma-separated list to indicate the bounding rectangle (x, y, width, and height) values:

<AbsoluteLayout>
    <BoxView
        AbsoluteLayout.LayoutBounds="90, 50, 75, 25"
        BackgroundColor="{x:Static c:Hues.Sky}" />
</AbsoluteLayout>

The parameters for the AbsoluteLayout.LayoutBounds attribute are as follows:

  • X: The horizontal position of the element
  • Y: The vertical position of the element
  • Width: The width of the element
  • Height: The height of the element

This snippet results in a 75-pixel wide, 25-pixel tall BoxView that is positioned 90-pixels left and 50-pixels down from upper-left corner of its parent.

As I said, hard-coded size and position is a throwback to older times. It's better to use a proportional size in the AbsoluteLayout. To switch to proportional layout, use the LayoutFlag attribute:

<AbsoluteLayout>
    <BoxView
        AbsoluteLayout.LayoutFlags="All"
        AbsoluteLayout.LayoutBounds="90, 50, 75, 25"
        BackgroundColor="{x:Static c:Hues.Sky}" />
</AbsoluteLayout>

This sets all four values in the LayoutBounds attribute to proportional size. Now the BoxView is 75% of the width of the parent, instead of 75-pixels. It's the same for the other three values; for example, the top of the BoxView is located at the halfway mark in the parent instead of 50-pixels from the top.

The All flag sets each value in the bounding box to proportional. There are other flags that switch on proportional values for the other bounding box values:

<AbsoluteLayout>
    <BoxView
        AbsoluteLayout.LayoutFlags="WidthProportional"
        AbsoluteLayout.LayoutBounds="90, 50, 75, 25"
        BackgroundColor="{x:Static c:Hues.Sky}" />
</AbsoluteLayout>

The WidthProportional flag sets the Width to a proportional value and the other three values remain absolute. Check out the Xamarin documentation to see all the combinations.

Proportional sizes are an improvement over the absolute sizes; an even better option is to use the RelativeLayout.

RelativeLayout

A RelativeLayout is useful for creating adaptive layouts where the UI adjusts to screen rotation and screen sizes. Use it to lay out UI elements based on their parent's position and dimensions as well as other views' positions and dimensions. Another key benefit of the RelativeLayout (and the AbsoluteLayout) is the ability to overlap or layer child elements.

Each element in the RelativeLayout can define a constraint, which is a rule that defines how the element relates to other items (parent, siblings).

There are several constraint types available:

  • RelativeLayout.YConstraint
  • RelativeLayout.XConstraint
  • RelativeLayout.WidthConstraint
  • RelativeLayout.HeightConstraint

The names are self-explanatory. It's not hard to understand what each constraint type does. Next, I'll look at several of the constraints to show you how they work.

Imagine a scenario where you have two views, a Label and a Button. The Label is positioned at the top of the RelativeLayout and set to 90% of the parent's width. The Button goes below the Label and matches the Label's width and is positioned at the left edge of the button. The width of the RelativeLayout changes when the user rotates the phone to landscape mode. The Label's width needs to adjust accordingly and the left position of the Button needs to change too.

A constraint can have the following values:

  • ElementName: The name of the element that the constraint is relative to
  • Type: Whether the constraint is relative to the parent or another view
  • Property: Which property on the relative element to use for the constraint
  • Factor: The multiplier for the constraint
  • Constant: An offset for the constraint

Here's the XAML for the Label:

<RelativeLayout BackgroundColor="{x:Static c:Hues.Sky}" >
    <Label x:Name="MainLabel" 
             Text="Example"
             BackgroundColor="Black" 
             FontSize="24"
    RelativeLayout.WidthConstraint="{ConstraintExpression 
                                     Type=RelativeToParent,
                                     Property=Width,
                                     Factor=0.9,
                                     Constant=0}"
    RelativeLayout.XConstraint="{ConstraintExpression
                                     Type=RelativeToParent,
                                     Property=X,
                                     Constant=20}"  />

The Label has two constraints, WidthConstraint and XConstraint. The Type=RelativeToParent parameter sets the constraint to the parent RelativeLayout. The width of the Label is tied to the parent width with the Property=Width value and the Factor=0.9 sets the Label width to 90% of the parent. The Property=X and Constant=20 values set the X position of the Label.

Here is the XAML for the Button:

<Button Text="Do Work"
    RelativeLayout.YConstraint="{ConstraintExpression
                                 Type=RelativeToView,
                                 ElementName=MainLabel,
                                 Property=Y,
                                 Constant=40}"
    RelativeLayout.XConstraint="{ConstraintExpression
                                 Type=RelativeToView,
                                 ElementName=MainLabel,
                                Property=X,
                                Constant=0}"   />

Note the use of Type=RelativeToView and ElementName=MainLabel to set the YConstraint and XConstraint to the MainLabel view. Next, set the Property=Y and Constant=40 values to position the Button 40 pixels below the Label. Also use the Property=X and Constant=0 values to keep the left edge of the Button aligned with the left edge of the Label.

Figure 7 shows the RelativeLayout in action on the Android emulator.

Figure 7: Label and Button and RelativeLayout
Figure 7: Label and Button and RelativeLayout

Figure 8 shows the same UI in landscape mode.

Figure 8:  Label and Button and RelativeLayout in Landscape mode
Figure 8: Label and Button and RelativeLayout in Landscape mode

Conclusion

Generally speaking, the Xamarin layout views follow a similar structure to other XAML frameworks. They're designed to work in a cross-platform situation, which results in some unfamiliar territory for XAML veterans. Each layout solves a specific problem. Once you understand how each one works, you'll find yourself layering them together to build the UI of your dreams.