Jaa


Summary of Chapter 26. Custom layouts

Note

This book was published in the spring of 2016, and has not been updated since then. There is much in the book that remains valuable, but some of the material is outdated, and some topics are no longer entirely correct or complete.

Xamarin.Forms includes several classes derived from Layout<View>:

  • StackLayout,
  • Grid,
  • AbsoluteLayout, and
  • RelativeLayout.

This chapter describes how to create your own classes that derive from Layout<View>.

An overview of layout

There is no centralized system that handles Xamarin.Forms layout. Each element is responsible for determining what its own size should be, and how to render itself within a particular area.

Parents and children

Every element that has children is responsible for positioning those children within itself. It is the parent that ultimately determines what size its children should be based on the size it has available and the size the child wants to be.

Sizing and positioning

Layout begins at the top of the visual tree with the page and then proceeds through all the branches. The most important public method in layout is Layout defined by VisualElement. Every element that is a parent to other elements calls Layout for each of its children to give the child a size and postition relative to itself in the form of a Rectangle value. These Layout calls propagate through the visual tree.

A call to Layout is required for an element to appear on the screen, and causes the following read-only properties to be set. They are consistent with the Rectangle passed to the method:

  • Bounds of type Rectangle
  • X of type double
  • Y of type double
  • Width of type double
  • Height of type double

Prior to the Layout call, Height and Width have mock values of –1.

A call to Layout also triggers calls to the following protected methods:

Finally, the following event is fired:

The OnSizeAllocated method is overridden by Page and Layout, which are the only two classes in Xamarin.Forms that can have children. The overridden method calls

LayoutChildren then calls Layout for each of the element's children. If at least one child has a new Bounds setting, then the following event is fired:

Constraints and size requests

For LayoutChildren to intelligently call Layout on all its children, it must know a preferred or desired size for the children. Therefore the calls to Layout for each of the children are generally preceded by calls to

After the book was published, the GetSizeRequest method was deprecated and replaced with

The Measure method accommodates the Margin property and includes an argument of type MeasureFlag, which has two members:

For many elements, GetSizeRequest or Measure obtains the native size of the element from its renderer. Both methods have parameters for width and height constraints. For example, a Label will use the width constraint to determine how to wrap multiple lines of text.

Both GetSizeRequestand Measure return a value of type SizeRequest, which has two properties:

Very often these two values are the same, and the Minimum value can usually be ignored.

VisualElement also defines a protected method similar to GetSizeRequest that is called from GetSizeRequest:

That method is now deprecated and replaced with:

Every class that derives from Layout or Layout<T> must override OnSizeRequest or OnMeasure. This is where a layout class determines its own size, which is generally based on the size of its children, which it obtains by calling GetSizeRequest or Measure on the children. Before and after calling OnSizeRequest or OnMeasure, GetSizeRequest or Measure makes adjustments based on the following properties:

Infinite constraints

The constraint arguments passed to GetSizeRequest (or Measure) and OnSizeRequest (or OnMeasure) can be infinite (i.e., values of Double.PositiveInfinity). However, the SizeRequest returned from these methods cannot contain infinite dimensions.

Infinite constraints indicate that the requested size should reflect the element's natural size. A vertical StackLayout calls GetSizeRequest (or Measure) on its children with an infinite height constraint. A horizontal stack layout calls GetSizeRequest (or Measure) on its children with an infinite width constraint. An AbsoluteLayout calls GetSizeRequest (or Measure) on its children with infinite width and height constraints.

Peeking inside the process

The ExploreChildSize displays constraint and size request information for a simple layout.

Deriving from Layout<View>

A custom layout class derives from Layout<View>. It has two responsibilities:

  • Override OnMeasure to call Measure on all the layout's children. Return a requested size for the layout itself
  • Override LayoutChildren to call Layout on all the layout's children

The for or foreach loop in these overrides should skip any child whose IsVisible property is set to false.

A call to OnMeasure is not guaranteed. OnMeasure will not be called if the parent of the layout is governing the layout's size (for example, a layout that fills a page). For this reason, LayoutChildren cannot rely on child sizes obtained during the OnMeasure call. Very often, LayoutChildren must itself call Measure on the layout's children, or you can implement some kind of size caching logic (to be discussed later).

An easy example

The VerticalStackDemo sample contains a simplified VerticalStack class and a demonstration of its use.

Vertical and horizontal positioning simplified

One of the jobs that VerticalStack must perform occurs during the LayoutChildren override. The method uses the child's HorizontalOptions property to determine how to position the child within its slot in the VerticalStack. You can instead call the static method Layout.LayoutChildIntoBoundingRect. This method calls Measure on the child and uses its HorizontalOptions and VerticalOptions properties to position the child within the specified rectangle.

Invalidation

Often a change in an element's property affects how that element appears in layout. The layout must be invalidated to trigger a new layout.

VisualElement defines a protected method InvalidateMeasure, which is generally called by the property-changed handler of any bindable property whose change affects the element's size. The InvalidateMeasure method fires a MeasureInvalidated event.

The Layout class defines a similar protected method named InvalidateLayout, which a Layout derivative should call for any change that affects how it positions and sizes its children.

Some rules for coding layouts

  1. Properties defined by Layout<T> derivatives should be backed by bindable properties, and the property-changed handlers should call InvalidateLayout.

  2. A Layout<T> derivative that defines attached bindable properties should override OnAdded to add a property-changed handler to its children and OnRemoved to remove that handler. The handler should check for changes in these attached bindable properties and respond by calling InvalidateLayout.

  3. A Layout<T> derivative that implements a cache of child sizes should override InvalidateLayout and OnChildMeasureInvalidated and clear the cache when these methods are called.

A layout with properties

The WrapLayout class in the Xamarin.FormsBook.Toolkit assumes that all its children are the same size, and wraps the children from one row (or column) to the next. It defines an Orientation property like StackLayout, and ColumnSpacing and RowSpacing properties like Grid, and it caches child sizes.

The PhotoWrap sample puts a WrapLayout in a ScrollView for displaying stock photos.

No unconstrained dimensions allowed!

The UniformGridLayout in the Xamarin.FormsBook.Toolkit library is intended to display all its children within itself. Therefore, it cannot deal with unconstrained dimensions and raises an exception if one is encountered.

The PhotoGrid sample demonstrates UniformGridLayout:

Triple screenshot of Photo Grid

Overlapping children

A Layout<T> derivative can overlap its children. However, the children are rendered in their order in the Children collection, and not the order in which their Layout methods are called.

The Layout class defines two methods that allow you to move a child within the collection:

  • LowerChild to move a child to the beginning of the collection
  • RaiseChild to move a child to the end of the collection

For overlapping children, children at the end of the collection visually appear on top of children at the beginning of the collection.

The OverlapLayout class in the Xamarin.FormsBook.Toolkit library defines an attached property to indicate the render order and thus allow one of its children to be displayed on top of the others. The StudentCardFile sample demonstrates this:

Triple screenshot of Student Card File Grid

More attached bindable properties

The CartesianLayout class in the Xamarin.FormsBook.Toolkit library defines attached bindable properties to specify two Point values and a thickness value and manipulates BoxView elements to resemble lines.

The UnitCube sample uses that to draw a 3D cube.

Layout and LayoutTo

A Layout<T> derivative can call LayoutTo rather than Layout to animate the layout. The AnimatedCartesianLayout class does this, and the AnimatedUnitCube sample demonstrates it.