Building a CarouselView using custom layouts

Xamarin.Forms is a very young layout system, meaning that the number of layouts is quite limited. There are times when we will need to implement our own custom layouts to give us control over exactly where and how our views and controls appear on screen. The requirement will come from situations where you need to improve performance on screens that display a lot of views and controls, and sometimes the standard layouts are not good enough. We want to implement our custom layouts to carry out the absolute minimum amount of work required to produce the required layout.

Note

All layouts derive from the Xamarin.Forms.Layout class, which provides the required mechanisms for adding and removing children internally as well as some key utilities for writing a layout.

Let's start by adding a new folder called Controls in the FireStorable project. Add a new file called CarouselLayout.cs and implement the first part as follows:

public class CarouselLayout : Layout<View>
  {
    #region Private Properties
      private IDisposable dataChangesSubscription;
      public double LayoutWidth;
    #endregion
  }

All layouts must inherit the Layout framework. Xamarin.Forms.Layout<T> provides a publicly exposed IList<T> Children that end users can access. We want all children of this collection to be of type View.

We have two private properties, one for the layout width and an IDisposable for handling data change subscriptions.

Let's add in some more properties:

#region Public Properties
public Object this[int index] 
  {
    get
    {
      return index < ItemsSource.Count() ? ItemsSource.ToList()[index] : null;
    }
  }
public DataTemplate ItemTemplate { get; set; }
public IEnumerable<Object> ItemsSource { get; set; }
#endregion

We have an indexing reference that will return an array element from the ItemsSource IEnumerable , and the ItemTemplate property, which is used to render a view layout for every child in ItemsSource. We have to use the Linq function ToList to allow us to access an IEnumerable via an index value.

Now we are going to add some overrides to the Layout framework. Every custom layout must override the LayoutChildren method. This is responsible for positioning children on screen:

protected override void LayoutChildren(double x, double y, double width, double height)
  { 
    var layout = ComputeLayout(width, height);
    var i = 0;
    foreach (var region in layout)
      { 
        var child = Children[i];
        i++;
        LayoutChildIntoBoundingRegion(child, region);
      }
    }

The preceding function will call another method, ComputeLayout, which will return an IEnumerable of Rectangles (also known as regions). We then iterate through the IEnumerable and call LayoutChildIntoBoundingRegion for each region. This method will handle positioning the element relative to the bounding region.

Our layout must also implement the OnMeasure function. This is required to make sure the new layout is sized correctly when placed inside other layouts. During layout cycles, this method may be called many times depending on the layout above it and how many layout exceptions are required to resolve the current layout hierarchy. Add the following below the LayoutChildren function:

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
  {
    List<Row> layout = ComputeNiaveLayout(widthConstraint, heightConstraint);
    var last = layout[layout.Count - 1];
    var width = (last.Count > 0) ?
    last[0].X + last.Width : 0; var height = (last.Count > 0) ? last[0].Y + 
    last.Height : 0;
    return new SizeRequest(new Size(width, height)); 
  }

Tip

It is therefore important to consider speed when implementing this function. Failure to implement this function will not always break your layout, particularly if it's always inside parents, which fix the child size anyway.

The ComputeNiaveLayout will return a list of rows. We then retrieve the last row from this list and use this for the max x-value and max y-value to determine the total width and height by calculating the difference between the first and last element on both the x-axis and y-axis. Finally, we return a new SizeRequest object with the calculated width and height, which will be used to resize the layout.

Let's add the missing functions ComputeNiaveLayout and ComputeLayout as follows:

public IEnumerable<Rectangle> ComputeLayout(double widthConstraint, double heightConstraint)
  {
    List<Row> layout = ComputeNiaveLayout(widthConstraint, heightConstraint);
    return layout.SelectMany(s => s); 
  }

This function is used simply to perform the SelectMany query. The ComputeNiaveLayout layout is where all the work is done. This will iterate through all children; it will create one row, and one rectangle inside this row that will size to the height of the layout and the width will equal the total of all children widths. All children will be positioned horizontally next to one another to the right of the screen, as shown in the following screenshot:

Building a CarouselView using custom layouts

But only one child will be visible on screen at any one time as each child is sized to the full height and width of the layout:

private List<Row> ComputeNiaveLayout(double widthConstraint, double heightConstraint) 
  {
    var result = new List<Row>();
    var row = new Row();
    result.Add(row);
    var spacing = 20;
    double y = 0;
    foreach (var child in Children) 
    {
       var request = child.Measure(double.PositiveInfinity,
       double.PositiveInfinity);
       if (row.Count == 0)
       {
         row.Add(new Rectangle(0, y, LayoutWidth, Height));
         row.Height = request.Request.Height; continue; 
       }
       var last = row[row.Count - 1];
       var x = last.Right + spacing;
       var childWidth = LayoutWidth;
       var childHeight = request.Request.Height;
       row.Add(new Rectangle(x, y, childWidth, Height));
       row.Width = x + childWidth; row.Height = Math.Max(row.Height, Height); 
       }
    return result; 
 }

Hold on! What if I have a lot of children? This means that they will be stacked horizontally past the width of the screen. What do we do now?

The idea of a carousel view is to only show one view at a time, when the user swipes left and right; the view on the left/right side of the current view will come onto screen while the current view will move out of view, as shown in the following screenshot:

Building a CarouselView using custom layouts

Even though we have a custom layout that presents children horizontally, how are we going to handle the swipe events and scroll control?

We will achieve scroll control via a ScrollView and create a custom renderer for handling swipe events.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.51.241