Xamarin Forms Layout Engine, Under The Hood

Xamarin Forms has its own layout engine that calculates the position of elements on the screen, converting layout coordinates internally and passing them to the native controls for each platform. While the conversion from a Xamarin Forms control to a native control is easy to follow via a renderer, the layout engine is not as obvious. There is no Grid Renderer or easily surfaced API on how Xamarin Forms chooses to layout controls. In the following post, I will go through a simple example of how a ContentPage, with a Grid and Label will appear on a native platform.

// The Simple Example of a Label in a Grid in a ContentPage
MainPage = new ContentPage() { Content = new Grid() { Children = { { new Label() } } } };

Initial Layout

On each platform, a page or a view must first be created to host the controls.

Android

Your MainActivity will inherit from FormsAppCompatActivity. In the OnCreate method it will create a LinearLayout and use the SetContentView to establish it as the native Android Layout.

_layout = new LinearLayout(BaseContext);
SetContentView(_layout);

When the MainPage is set, it trigger functions that eventually reach InternalSetPage. _platform contains a renderer, which inherits from an Android ViewGroup, which is returned. The platform is then added to the LinearLayout.

_platform = new AppCompat.Platform(this);
if (_application != null)
    _application.Platform = _platform;

_platform.SetPage(page);
_layout.AddView(_platform);

iOS

Your AppDelegate inherits from FormsApplicationDelegate, which creates a UIWindow.

_window = new UIWindow(UIScreen.MainScreen.Bounds);

Following that, when MainPage is set, a ViewController is created and assigned to the RootViewController.

var platformRenderer = (PlatformRenderer)_window.RootViewController;
_window.RootViewController = _application.MainPage.CreateViewController();

The CreateViewController is an extension, that takes in a Xamarin Forms page and returns a UIViewController.

UWP

UWP starts with a MainPage which inherits from WindowsPage > WindowsBasePage > Windows.UI.Xaml.Controls.Page. Following the startup sequence you will see that the first element on the page is a Canvas.

_container = new Canvas { Style = (Windows.UI.Xaml.Style)Windows.UI.Xaml.Application.Current.Resources["RootContainerStyle"] };
_page.Content = _container;

When you set the MainPage, it will call SetPage,which in turns calls SetCurrent, where the container gets the native control to add to the _container. (Which is a Canvas)

_container.Children.Add(pageRenderer.ContainerElement);

ContentPage

The ContentPage is a simple class, for holding Xamarin Forms visual elements. The ContentPage itself is also a VisualElement. A VisualElement holds all of the visual details of an element, including things such as the X and Y coordinates, and height and width.

contentpage

The ContentPage has the Content property where you can place one VisualElement. In most circumstances, because we want to place multiple elements on the screen, we will place a Layout element in the Content property. This could be a Frame, StackLayout, Grid, RelativeLayout, AbsoluteLayout or others. For a complete list see Xamarin.Forms Layouts. The difference with these visual elements, is they inherit from Layout<T> or Layout.

The ContentPage doesn’t have a renderer, but Page does, and hence the PageRenderer is called to render the page. The PageRenderer itself, inherits from a native element that can be placed on the native page.

Android

The Android PageRenderer inherits from VisualElementRenderer<Page> then FormsViewGroup then AView.

When the MainPage is set, you may remember it performs this line.

_platform.SetPage(page);

Inside this function it calls another function, AddChild. Here is where it will get the PageRenderer and add the ViewGroup from the PageRenderer and add it to the view.

void AddChild(Page page, bool layout = false)
{
    if (Android.Platform.GetRenderer(page) != null)
        return;

    Android.Platform.SetPageContext(page, _context);
    IVisualElementRenderer renderView = Android.Platform.CreateRenderer(page);
    Android.Platform.SetRenderer(page, renderView);

    if (layout)
        LayoutRootPage(page, _renderer.Width, _renderer.Height);

    _renderer.AddView(renderView.ViewGroup);
}

iOS

The iOS PageRenderer inherits from a UIViewController.

When the extension CreateViewController gets called when the MainPage is set.

_window.RootViewController = _application.MainPage.CreateViewController();

It will follow the path CreateViewController > Platform.SetPage > AddChild.

Following a similar pattern, the AddChild method will get the renderer and set the UIViewController and UIView.

void AddChild(VisualElement view)
{
    if (!Application.IsApplicationOrNull(view.RealParent))
        Console.Error.WriteLine("Tried to add parented view to canvas directly");

    if (GetRenderer(view) == null)
    {
        var viewRenderer = CreateRenderer(view);
        SetRenderer(view, viewRenderer);

        _renderer.View.AddSubview(viewRenderer.NativeView);
        if (viewRenderer.ViewController != null)
            _renderer.AddChildViewController(viewRenderer.ViewController);
        viewRenderer.NativeView.Frame = new RectangleF(0, 0, _renderer.View.Bounds.Width, _renderer.View.Bounds.Height);
        viewRenderer.SetElementSize(new Size(_renderer.View.Bounds.Width, _renderer.View.Bounds.Height));
    }
    else
    Console.Error.WriteLine("Potential view double add");
}

UWP

The UWP PageRenderer inherits from VisualElementRenderer<Page, FrameworkElement> then Panel.

In UWP when you set the MainPage, as detailed above, it add’s a child element to the Canvas.

_container.Children.Add(pageRenderer.ContainerElement);

The PageRenderer is called in Platform.SetCurrent, where it will create the renderer and hence Panel.

IVisualElementRenderer pageRenderer = newPage.GetOrCreateRenderer();
_container.Children.Add(pageRenderer.ContainerElement);

Rendering Controls

The next step to understand the process is how controls are rendered in this sequence. The rendering process works in a hierarchy of top to bottom. In our example, the renderers will complete in this sequence, PageRenderer (Page) > LayoutRenderer (Grid) > LabelRenderer (Label). Due to the complexity of how many methods are triggered, a hierarchical call stack will be shown in its simplest way possible. Each method does perform more than I am showing here, but it is not relevant to this example.

Android

Platform.cs
   - SetPage
       - AddChild
           - CreateRenderer
               - SetElement
                    - foreach LogicalChildren
                        - AddChild // NOTE: Recursive
       - _renderer.AddView(renderView.ViewGroup)

iOS

PageRenderer which is a UIViewController
   - ViewDidLoad
       - Packager = new VisualElementPackager(this)
       - Packager.Load()
         foreach LogicalChildren
            OnChildAdded
                - Platform.CreateRenderer()
                    - SetElement [VisualElementRenderer.cs]
                        - OnElementChanged
            uiView.AddSubview(renderer.NativeView)

UWP

Platform.cs
    - SetCurrent
        - GetOrCreateRenderer
            - CreateRenderer // This is the PageRenderer during the first run.
                - SetElement
                    - Packager = new VisualElementPackager(this)
                    - Packager.Load()
                         foreach ElementController.LogicalChildren // contains the XF controls
                             OnChildAdded(VisualElementPackager.cs) // Sets Grid Row/Col properties here [_panel.Children.Add(the button)]
                                   - CreateRenderer (in Platform.cs) // for the child control NOTE: Recursion back to the first CreateRenderer method
                                   - _panel.Children.Add(childRenderer.ContainerElement)

      - _container.Children.Add(...) // Contains all the built elements through the renderers

Layout

Now moving on to the layout system and how the layout elements actually position the controls on the screen. The Grid, StackLayout and others all inherit from the class Layout, which goes all the way down to a VisualElement.

grid

The inheritance from Layout is important, because that is the first object a render is detected for. Hence the LayoutRenderer is what will render the Grid. The LayoutRenderer will inherit all the way back to a VisualElementRenderer. These renderers are platform specific and hence each VisualElementRenderer will inherit from a native control.

layout

UWP inherits from Panel, Android inherits from a FormsViewGroup and iOS inherits from a UIView. But don’t be fooled into thinking these renderers handle any of the actual layout. At most they provide a container for child VisualElements.

Grid

The Grid is one of the most commonly used layout elements, but as previously mentioned, there is no Grid renderer, not even the LayoutRenderer provides the layout calculations needed. The positions of elements inside a Grid is all done inside the Xamarin.Forms.Core assembly, in Grid.cs and GridCalc.cs. When the bounds are set in the VisualElement upon rendering, it starts the layout process. Bounds > SetSize > SizeAllocated > OnSizeAllocated. This continues on as below.
gridcalclayout

When the Bounds of the VisualElement are changed, via the LayoutChildIntoBoundingRegion, via child.Layout(), it calls the SizeChanged event. The SizeChanged event is listened to in Platform.cs, which allows the platform specific code to change the native page size. The ViewRenderer contains OnLayout, which provides a way for native elements to have their layout bounds changed.

Looking at the code in GridCalc.cs we can see that it isn’t using any control to layout the elements, it calculates their position and sets the Bounds for each element.

protected override void LayoutChildren(double x, double y, double width, double height)
{
    if (!InternalChildren.Any())
        return;

    MeasureGrid(width, height);

    // Make copies so if InvalidateMeasure is called during layout we dont crash when these get nulled
    List<ColumnDefinition> columnsCopy = _columns;
    List<RowDefinition> rowsCopy = _rows;

    for (var index = 0; index < InternalChildren.Count; index++)
    {
        var child = (View)InternalChildren[[index]];
        if (!child.IsVisible)
            continue;
        int r = GetRow(child);
        int c = GetColumn(child);
        int rs = GetRowSpan(child);
        int cs = GetColumnSpan(child);

        double posx = x + c * ColumnSpacing;
        for (var i = 0; i < c; i++)
            posx += columnsCopy[[i]].ActualWidth;
        double posy = y + r * RowSpacing;
        for (var i = 0; i < r; i++)
            posy += rowsCopy[[i]].ActualHeight;

        double w = columnsCopy[c].ActualWidth;
        for (var i = 1; i < cs; i++)
            w += ColumnSpacing + columnsCopy[c + i].ActualWidth;
        double h = rowsCopy[[r]].ActualHeight;
        for (var i = 1; i < rs; i++)
            h += RowSpacing + rowsCopy[[r + i]].ActualHeight;

        // in the future we can might maybe optimize by passing the already calculated size request
        LayoutChildIntoBoundingRegion(child, new Rectangle(posx, posy, w, h));
    }
}

Summary

This has been an under the hood look at how the layout system manages to place Xamarin Forms Elements into native controls and set the native bounds. We haven’t looked at the Layout Cycle and what happens when an element is changed and forces a change in other elements, this will be left to another post. If you have any questions or comments, please leave them down below.

Microsoft MVP | Xamarin MVP | Xamarin Certified Developer |
Exrin MVVM Framework | Xamarin Forms Developer | Melbourne, Australia


XAMARIN.FORMS MONTHLY NEWSLETTER

JOIN 1,000+ SUBSCRIBERS

  1. Don't miss out on updates
  2. The latest info from this site, Xamarin and the community
  3. Unsubscribe at any time*

* We use MailChimp, with double opt-in, and instant unsubscribe

Related Posts

One Comment

Leave A Comment?