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
, andRelativeLayout
.
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 typeRectangle
X
of typedouble
Y
of typedouble
Width
of typedouble
Height
of typedouble
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:
SizeAllocated
, which callsOnSizeAllocated
, which can be overridden.
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
UpdateChildrenLayout
forPage
derivatives andUpdateChildrenLayout
forLayout
derivatives, which callsLayoutChildren
forPage
derivatives andLayoutChildren
forLayout
derivatives.
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:
LayoutChanged
forPage
derivatives andLayoutChanged
forLayout
derivatives
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:
IncludeMargins
None
to not include margins
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 GetSizeRequest
and 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
:
OnSizeRequest
returns aSizeRequest
value
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:
WidthRequest
of typedouble
, affects theRequest
property ofSizeRequest
HeightRequest
of typedouble
, affects theRequest
property ofSizeRequest
MinimumWidthRequest
of typedouble
, affects theMinimum
property ofSizeRequest
MinimumHeightRequest
of typedouble
, affects theMinimum
property ofSizeRequest
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 callMeasure
on all the layout's children. Return a requested size for the layout itself - Override
LayoutChildren
to callLayout
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
Properties defined by
Layout<T>
derivatives should be backed by bindable properties, and the property-changed handlers should callInvalidateLayout
.A
Layout<T>
derivative that defines attached bindable properties should overrideOnAdded
to add a property-changed handler to its children andOnRemoved
to remove that handler. The handler should check for changes in these attached bindable properties and respond by callingInvalidateLayout
.A
Layout<T>
derivative that implements a cache of child sizes should overrideInvalidateLayout
andOnChildMeasureInvalidated
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
:
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 collectionRaiseChild
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:
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.