Skip to content

Instantly share code, notes, and snippets.

@rasmuschristensen
Last active May 4, 2017 17:11
Show Gist options
  • Save rasmuschristensen/4078558c7e0ce7ac801aaa8735920831 to your computer and use it in GitHub Desktop.
Save rasmuschristensen/4078558c7e0ce7ac801aaa8735920831 to your computer and use it in GitHub Desktop.
Xamarin Forms WrapPanel raw 1st edition, not complete yet.....
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Xamarin.Forms;
namespace Controls.Bypassion.dk
{
public class WrapLayout : Layout<View>
{
public static readonly BindableProperty SpacingProperty =
BindableProperty.Create (nameof (Spacing), typeof (double), typeof (WrapLayout), 0.0,
propertyChanged: OnSpacingChanged);
public static readonly BindableProperty RowSpacingProperty =
BindableProperty.Create (nameof (RowSpacing), typeof (double), typeof (WrapLayout), 0.0,
propertyChanged: OnRowSpacingChanged);
public static readonly BindableProperty MaxColumnsProperty =
BindableProperty.Create (nameof (MaxColumns), typeof (int), typeof (WrapLayout), 1);
public static readonly BindableProperty HasUnevenCellsProperty =
BindableProperty.Create (nameof (HasUnevenCells), typeof (bool), typeof (WrapLayout), false);
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create (nameof (ItemsSource), typeof (IList), typeof (WrapLayout), default (IList),
propertyChanged: (bindableObject, oldValue, newValue) => {
((WrapLayout)bindableObject).ItemsSourceChanged (bindableObject, (IList)oldValue, (IList)newValue);
});
public static readonly BindableProperty ItemTemplateProperty =
BindableProperty.Create (nameof (ItemTemplate), typeof (DataTemplate), typeof (WrapLayout), default (DataTemplate));
public static readonly BindableProperty ItemTemplateSelctorProperty =
BindableProperty.Create (nameof (ItemTemplateSelctor), typeof (DataTemplateSelector), typeof (WrapLayout), default (DataTemplateSelector));
private static event EventHandler<NotifyCollectionChangedEventArgs> _collectionChanged;
public Dictionary<Guid, object> mappings { get; set; }
public WrapLayout ()
{
mappings = new Dictionary<Guid, object> ();
}
private static void OnSpacingChanged (BindableObject bindable, object oldvalue, object newvalue)
{
var self = (WrapLayout)bindable;
self.InvalidateMeasure ();
}
private static void OnRowSpacingChanged (BindableObject bindable, object oldvalue, object newvalue)
{
var self = (WrapLayout)bindable;
self.InvalidateMeasure ();
}
public IList ItemsSource {
get {
return (IList)GetValue (ItemsSourceProperty);
}
set {
SetValue (ItemsSourceProperty, value);
}
}
public double Spacing {
get { return (double)GetValue (SpacingProperty); }
set { SetValue (SpacingProperty, value); }
}
public double RowSpacing {
get { return (double)GetValue (RowSpacingProperty); }
set { SetValue (RowSpacingProperty, value); }
}
public int MaxColumns {
get { return (int)GetValue (MaxColumnsProperty); }
set { SetValue (MaxColumnsProperty, value); }
}
public bool HasUnevenCells {
get { return (bool)GetValue (HasUnevenCellsProperty); }
set { SetValue (HasUnevenCellsProperty, value); }
}
public DataTemplate ItemTemplate {
get { return (DataTemplate)GetValue (ItemTemplateProperty); }
set { SetValue (ItemTemplateProperty, value); }
}
public DataTemplateSelector ItemTemplateSelctor {
get { return (DataTemplateSelector)GetValue (ItemTemplateSelctorProperty); }
set { SetValue (ItemTemplateSelctorProperty, value); }
}
void ItemsSourceChanged (BindableObject bindable, IList oldValue, IList newValue)
{
if (ItemsSource == null)
return;
if (oldValue != null) {
foreach (var oldItem in oldValue) {
// no more listening for prop change events on old items
((INotifyPropertyChanged)oldItem).PropertyChanged -= OnPropertyChanged;
}
}
var notifyCollection = newValue as INotifyCollectionChanged;
if (notifyCollection != null) {
BuildChildren (newValue);
}
}
/// <summary>
/// Build view's for each item in the list and
/// </summary>
/// <param name="newItems"></param>
private void BuildChildren (IList newItems)
{
Children.Clear ();
foreach (var newItem in newItems) {
// initial listening for prop changes
// ((INotifyPropertyChanged)newItem).PropertyChanged += OnPropertyChanged;
//build a view to represent the item
var view = BuildChild (newItem);
// finally add it to our collection
Children.Add (view);
}
}
private View BuildChild (object item)
{
// get template by selection
object child;
if (ItemTemplateSelctor != null) {
var template = ItemTemplateSelctor.SelectTemplate (item, null);
child = template.CreateContent ();
} else {
child = ItemTemplate.CreateContent ();
//child = ((DataTemplate)item).CreateContent ();
}
// support view and viewcell
View view;
var vc = child as ViewCell;
if (vc != null) { view = vc.View; } else {
view = (View)child;
}
// need a bindingcontext?
var bindableView = (BindableObject)view;
if (bindableView != null) {
bindableView.BindingContext = item;
}
return view;
}
private void OnPropertyChanged (object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (ItemTemplateSelctor != null) // todo could add specific property to test against
{
var view = BuildChild (sender);
for (int i = 0; i < Children.Count; i++) {
if (Children [i].BindingContext == sender) {
Children [i] = view;
break;
}
}
}
}
protected override SizeRequest OnMeasure (double widthConstraint, double heightConstraint)
{
var layout = ComputeNaiveLayout (widthConstraint, heightConstraint);
var width = layout.Max (row => row.Width);
var last = layout [layout.Count - 1];
var height = last.Any () ? last [0].Y + last.Height : 0;
return new SizeRequest (new Size (width, height));
}
protected override void LayoutChildren (double x, double y, double width, double height)
{
var layout = ComputeLayout (width, height);
int i = 0;
foreach (var region in layout) {
var child = Children [i];
i++;
LayoutChildIntoBoundingRegion (child, region);
}
}
private IEnumerable<Rectangle> ComputeLayout (double widthConstraint, double heightConstraint)
{
var layout = ComputeNaiveLayout (widthConstraint, heightConstraint);
CenterLayout (layout, widthConstraint, heightConstraint);
foreach (var row in layout) {
foreach (Rectangle rect in row) {
yield return rect;
}
}
}
private void CenterLayout (List<Row> layout, double widthConstraint, double heightConstraint)
{
foreach (var row in layout) {
// if there is only a single element, just left align it
var leftPad = row.Count < MaxColumns ? 0 : (widthConstraint - row.Width) / 2;
for (int i = 0; i < row.Count; i++) {
var region = row [i];
region.X += leftPad;
region.Height = row.Height;
row [i] = region;
}
}
}
private List<Row> ComputeNaiveLayout (double widthConstraint, double heightConstraint)
{
// column support, ensure to make enough spacing
var maxColumns = MaxColumns;
var boxWidth = (widthConstraint - (Spacing * (maxColumns - 1))) / maxColumns;
var result = new List<Row> ();
var row = new Row ();
result.Add (row);
var spacing = Spacing;// avoid accessing the property on each using by extracting it here
var hasUnevenCells = HasUnevenCells;
double y = 0; // top first
foreach (var child in Children) {
var request = child.Measure (double.PositiveInfinity, double.PositiveInfinity);
var requestWidth = hasUnevenCells ? request.Request.Width : boxWidth;
var requestHeight = requestWidth;// request.Request.Height;
if (row.Count == 0) // the very first
{
row.Add (new Rectangle (0, y, requestWidth, requestHeight));
row.Height = requestHeight;
continue;
}
var last = row [row.Count - 1];
var x = last.Right + spacing;
//check for wrapping
if (x + requestWidth > widthConstraint) {
y += row.Height + spacing + RowSpacing;
row = new Row ();
result.Add (row);
x = 0;
}
row.Add (new Rectangle (x, y, requestWidth, requestHeight));
row.Width = x + requestWidth;
row.Height = Math.Max (row.Height, requestHeight);
}
return result;
}
class Row : List<Rectangle>
{
public double Width { get; set; }
public double Height { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment