Skip to content

Instantly share code, notes, and snippets.

@NicoVermeir
Created April 2, 2015 18:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save NicoVermeir/7ffb34ebd639ed958382 to your computer and use it in GitHub Desktop.
Save NicoVermeir/7ffb34ebd639ed958382 to your computer and use it in GitHub Desktop.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Xamarin.Forms;
namespace WrapPanelDemo.Controls
{
public class AwesomeWrappanel : Layout<View>
{
private static event EventHandler<NotifyCollectionChangedEventArgs> _collectionChanged;
/// <summary>
/// Backing Storage for the Orientation property
/// </summary>
public static readonly BindableProperty OrientationProperty =
BindableProperty.Create<AwesomeWrappanel, StackOrientation>(w => w.Orientation, StackOrientation.Vertical,
propertyChanged: (bindable, oldvalue, newvalue) => ((AwesomeWrappanel)bindable).OnSizeChanged());
/// <summary>
/// Orientation (Horizontal or Vertical)
/// </summary>
public StackOrientation Orientation
{
get { return (StackOrientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Backing Storage for the Spacing property
/// </summary>
public static readonly BindableProperty SpacingProperty =
BindableProperty.Create<AwesomeWrappanel, double>(w => w.Spacing, 6,
propertyChanged: (bindable, oldvalue, newvalue) => ((AwesomeWrappanel)bindable).OnSizeChanged());
/// <summary>
/// Spacing added between elements (both directions)
/// </summary>
/// <value>The spacing.</value>
public double Spacing
{
get { return (double)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
/// <summary>
/// Backing Storage for the Spacing property
/// </summary>
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.Create<AwesomeWrappanel, DataTemplate>(w => w.ItemTemplate, null,
propertyChanged: (bindable, oldvalue, newvalue) => ((AwesomeWrappanel)bindable).OnSizeChanged());
/// <summary>
/// Spacing added between elements (both directions)
/// </summary>
/// <value>The spacing.</value>
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
/// <summary>
/// Backing Storage for the Spacing property
/// </summary>
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create<AwesomeWrappanel, IEnumerable>(w => w.ItemsSource, null,
propertyChanged: ItemsSource_OnPropertyChanged);
/// <summary>
/// Spacing added between elements (both directions)
/// </summary>
/// <value>The spacing.</value>
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
private static void ItemsSource_OnPropertyChanged(BindableObject bindable, IEnumerable oldvalue, IEnumerable newvalue)
{
if (oldvalue != null)
{
var coll = (INotifyCollectionChanged)oldvalue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= ItemsSource_OnItemChanged;
}
if (newvalue != null)
{
var coll = (INotifyCollectionChanged)newvalue;
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += ItemsSource_OnItemChanged;
}
}
public AwesomeWrappanel()
{
_collectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
foreach (object item in args.NewItems)
{
var child = ItemTemplate.CreateContent() as View;
if (child == null)
return;
child.BindingContext = item;
Children.Add(child);
}
}
private static void ItemsSource_OnItemChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_collectionChanged != null)
_collectionChanged(null, e);
}
/// <summary>
/// This is called when the spacing or orientation properties are changed - it forces
/// the control to go back through a layout pass.
/// </summary>
private void OnSizeChanged()
{
ForceLayout();
}
/// <summary>
/// This method is called during the measure pass of a layout cycle to get the desired size of an element.
/// </summary>
/// <param name="widthConstraint">The available width for the element to use.</param>
/// <param name="heightConstraint">The available height for the element to use.</param>
protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
{
if (WidthRequest > 0)
widthConstraint = Math.Min(widthConstraint, WidthRequest);
if (HeightRequest > 0)
heightConstraint = Math.Min(heightConstraint, HeightRequest);
double internalWidth = double.IsPositiveInfinity(widthConstraint) ? double.PositiveInfinity : Math.Max(0, widthConstraint);
double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);
return Orientation == StackOrientation.Vertical
? DoVerticalMeasure(internalWidth, internalHeight)
: DoHorizontalMeasure(internalWidth, internalHeight);
}
/// <summary>
/// Does the vertical measure.
/// </summary>
/// <returns>The vertical measure.</returns>
/// <param name="widthConstraint">Width constraint.</param>
/// <param name="heightConstraint">Height constraint.</param>
private SizeRequest DoVerticalMeasure(double widthConstraint, double heightConstraint)
{
int columnCount = 1;
double width = 0;
double height = 0;
double minWidth = 0;
double minHeight = 0;
double heightUsed = 0;
foreach (var item in Children)
{
var size = item.GetSizeRequest(widthConstraint, heightConstraint);
width = Math.Max(width, size.Request.Width);
var newHeight = height + size.Request.Height + Spacing;
if (newHeight > heightConstraint)
{
columnCount++;
heightUsed = Math.Max(height, heightUsed);
height = size.Request.Height;
}
else
height = newHeight;
minHeight = Math.Max(minHeight, size.Minimum.Height);
minWidth = Math.Max(minWidth, size.Minimum.Width);
}
if (columnCount > 1)
{
height = Math.Max(height, heightUsed);
width *= columnCount; // take max width
}
return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight));
}
/// <summary>
/// Does the horizontal measure.
/// </summary>
/// <returns>The horizontal measure.</returns>
/// <param name="widthConstraint">Width constraint.</param>
/// <param name="heightConstraint">Height constraint.</param>
private SizeRequest DoHorizontalMeasure(double widthConstraint, double heightConstraint)
{
int rowCount = 1;
double width = 0;
double height = 0;
double minWidth = 0;
double minHeight = 0;
double widthUsed = 0;
foreach (var item in Children)
{
var size = item.GetSizeRequest(widthConstraint, heightConstraint);
height = Math.Max(height, size.Request.Height);
var newWidth = width + size.Request.Width + Spacing;
if (newWidth > widthConstraint)
{
rowCount++;
widthUsed = Math.Max(width, widthUsed);
width = size.Request.Width;
}
else
width = newWidth;
minHeight = Math.Max(minHeight, size.Minimum.Height);
minWidth = Math.Max(minWidth, size.Minimum.Width);
}
if (rowCount > 1)
{
width = Math.Max(width, widthUsed);
height = (height + Spacing) * rowCount - Spacing; // via MitchMilam
}
return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight));
}
/// <summary>
/// Positions and sizes the children of a Layout.
/// </summary>
/// <param name="x">A value representing the x coordinate of the child region bounding box.</param>
/// <param name="y">A value representing the y coordinate of the child region bounding box.</param>
/// <param name="width">A value representing the width of the child region bounding box.</param>
/// <param name="height">A value representing the height of the child region bounding box.</param>
protected override void LayoutChildren(double x, double y, double width, double height)
{
if (Orientation == StackOrientation.Vertical)
{
double colWidth = 0;
double yPos = y, xPos = x;
foreach (var child in Children.Where(c => c.IsVisible))
{
var request = child.GetSizeRequest(width, height);
double childWidth = request.Request.Width;
double childHeight = request.Request.Height;
colWidth = Math.Max(colWidth, childWidth);
if (yPos + childHeight > height)
{
yPos = y;
xPos += colWidth + Spacing;
colWidth = 0;
}
var region = new Rectangle(xPos, yPos, childWidth, childHeight);
LayoutChildIntoBoundingRegion(child, region);
yPos += region.Height + Spacing;
}
}
else
{
double rowHeight = 0;
double yPos = y, xPos = x;
foreach (var child in Children.Where(c => c.IsVisible))
{
var request = child.GetSizeRequest(width, height);
double childWidth = request.Request.Width;
double childHeight = request.Request.Height;
rowHeight = Math.Max(rowHeight, childHeight);
if (xPos + childWidth > width)
{
xPos = x;
yPos += rowHeight + Spacing;
rowHeight = 0;
}
var region = new Rectangle(xPos, yPos, childWidth, childHeight);
LayoutChildIntoBoundingRegion(child, region);
xPos += region.Width + Spacing;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment