Last active
May 4, 2017 17:11
-
-
Save rasmuschristensen/4078558c7e0ce7ac801aaa8735920831 to your computer and use it in GitHub Desktop.
Xamarin Forms WrapPanel raw 1st edition, not complete yet.....
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.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