-
-
Save scolestock/88fa8290c965476d802f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Specialized; | |
using System.Linq; | |
using Xamarin.Forms; | |
namespace WrapPanelDemo.Controls | |
{ | |
public class AwesomeWrappanel : Layout<View> | |
{ | |
public static readonly BindableProperty SpacingProperty = | |
BindableProperty.Create<WrapPanel, double>( | |
w => w.Spacing, | |
6, | |
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapPanel)bindable).OnSizeChanged()); | |
public static readonly BindableProperty ItemTemplateProperty = | |
BindableProperty.Create<WrapPanel, DataTemplate>( | |
w => w.ItemTemplate, | |
null, | |
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapPanel)bindable).OnSizeChanged()); | |
public static readonly BindableProperty ItemsSourceProperty = | |
BindableProperty.Create<WrapPanel, IEnumerable>( | |
w => w.ItemsSource, | |
null, | |
propertyChanged: (bindable, oldValue, 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; | |
// We might already have items in the collection prior to it being bound, so let's feed them into the control. | |
foreach (var item in newValue) | |
{ | |
ItemsSource_OnItemChanged(bindable, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); | |
} | |
} | |
}); | |
public static readonly BindableProperty OrientationProperty = | |
BindableProperty.Create<WrapPanel, StackOrientation>( | |
w => w.Orientation, | |
StackOrientation.Vertical, | |
propertyChanged: (bindable, oldvalue, newvalue) => ((WrapPanel)bindable).OnSizeChanged()); | |
public AwesomeWrappanel() | |
{ | |
// Make sure this instance of this class hears about changes to the bindable items source. | |
_collectionChanged += this.OnCollectionChanged; | |
} | |
private static event EventHandler<NotifyCollectionChangedEventArgs> _collectionChanged = delegate { }; | |
public int MaxChildrenDisplayed { get; set; } | |
/// <summary> | |
/// Gets or sets the orientation (Horizontal or Vertical) | |
/// </summary> | |
public StackOrientation Orientation | |
{ | |
get | |
{ | |
return (StackOrientation)GetValue(OrientationProperty); | |
} | |
set | |
{ | |
this.SetValue(OrientationProperty, value); | |
} | |
} | |
/// <summary> | |
/// Gets or sets the spacing added between elements (both directions) | |
/// </summary> | |
public double Spacing | |
{ | |
get | |
{ | |
return (double)GetValue(SpacingProperty); | |
} | |
set | |
{ | |
this.SetValue(SpacingProperty, value); | |
} | |
} | |
/// <summary> | |
/// Gets or sets the item template to use | |
/// </summary> | |
public DataTemplate ItemTemplate | |
{ | |
get | |
{ | |
return (DataTemplate)GetValue(ItemTemplateProperty); | |
} | |
set | |
{ | |
this.SetValue(ItemTemplateProperty, value); | |
} | |
} | |
/// <summary> | |
/// Gets or sets itemsSource to use | |
/// </summary> | |
public IEnumerable ItemsSource | |
{ | |
get | |
{ | |
return (IEnumerable)GetValue(ItemsSourceProperty); | |
} | |
set | |
{ | |
this.SetValue(ItemsSourceProperty, value); | |
} | |
} | |
/// <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 (this.WidthRequest > 0) | |
{ | |
widthConstraint = Math.Min(widthConstraint, this.WidthRequest); | |
} | |
if (this.HeightRequest > 0) | |
{ | |
heightConstraint = Math.Min(heightConstraint, this.HeightRequest); | |
} | |
double internalWidth = double.IsPositiveInfinity(widthConstraint) ? double.PositiveInfinity : Math.Max(0, widthConstraint); | |
double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint); | |
return this.Orientation == StackOrientation.Vertical | |
? this.DoVerticalMeasure(internalWidth, internalHeight) | |
: this.DoHorizontalMeasure(internalWidth, internalHeight); | |
} | |
/// <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 (this.Orientation == StackOrientation.Vertical) | |
{ | |
double colWidth = 0; | |
double positionY = y, positionX = 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 (positionY + childHeight > height) | |
{ | |
positionY = y; | |
positionX += colWidth + this.Spacing; | |
colWidth = 0; | |
} | |
var region = new Rectangle(positionX, positionY, childWidth, childHeight); | |
WrapPanel.LayoutChildIntoBoundingRegion(child, region); | |
positionY += region.Height + this.Spacing; | |
} | |
} | |
else | |
{ | |
double rowHeight = 0; | |
double positionY = y, positionX = 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 (positionX + childWidth > width) | |
{ | |
positionX = x; | |
positionY += rowHeight + this.Spacing; | |
rowHeight = 0; | |
} | |
var region = new Rectangle(positionX, positionY, childWidth, childHeight); | |
WrapPanel.LayoutChildIntoBoundingRegion(child, region); | |
positionX += region.Width + this.Spacing; | |
} | |
} | |
} | |
private static void ItemsSource_OnItemChanged(object sender, NotifyCollectionChangedEventArgs e) | |
{ | |
// This will wind up delivering to a given WrapPanel instance OnCollectionChanged method. | |
_collectionChanged(sender, e); | |
} | |
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) | |
{ | |
if (sender != this) | |
{ | |
// This is an event for a collection associated with a different instance of the wrap panel. | |
return; | |
} | |
if (args.Action == NotifyCollectionChangedAction.Reset) | |
{ | |
this.Children.Clear(); | |
} | |
else | |
{ | |
foreach (object item in args.NewItems) | |
{ | |
var child = this.ItemTemplate.CreateContent() as View; | |
if (child == null) | |
{ | |
return; | |
} | |
child.BindingContext = item; | |
Children.Add(child); | |
} | |
} | |
} | |
/// <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() | |
{ | |
this.ForceLayout(); | |
} | |
/// <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 this.Children) | |
{ | |
var size = item.GetSizeRequest(widthConstraint, heightConstraint); | |
width = Math.Max(width, size.Request.Width); | |
var newHeight = height + size.Request.Height + this.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 this.Children) | |
{ | |
var size = item.GetSizeRequest(widthConstraint, heightConstraint); | |
height = Math.Max(height, size.Request.Height); | |
var newWidth = width + size.Request.Width + this.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 + this.Spacing) * rowCount) - this.Spacing; // via MitchMilam | |
} | |
return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment