using System; using System.Collections.Generic; using Xamarin.Forms; namespace InABox.Mobile { struct LayoutData { public int VisibleChildCount { get; private set; } public Size CellSize { get; private set; } public int Rows { get; private set; } public int Columns { get; private set; } public LayoutData(int visibleChildCount, Size cellSize, int rows, int columns) { VisibleChildCount = visibleChildCount; CellSize = cellSize; Rows = rows; Columns = columns; } } public enum ContentAlignment { Start = 0, Center, End } public class WrapLayout : Layout { Dictionary layoutDataCache = new Dictionary(); public static readonly BindableProperty CellPaddingProperty = BindableProperty.Create( "CellPadding", typeof(double), typeof(WrapLayout), 10.0, propertyChanged: (bindable, oldvalue, newvalue) => { ((WrapLayout)bindable).InvalidateLayout(); }); public double CellPadding { set { SetValue(CellPaddingProperty, value); } get { return (double)GetValue(CellPaddingProperty); } } public static readonly BindableProperty HorizontalContentAlignmentProperty = BindableProperty.Create( "HorizontalContentAlignment", typeof(ContentAlignment), typeof(WrapLayout), ContentAlignment.Center, propertyChanged: (bindable, oldvalue, newvalue) => { ((WrapLayout)bindable).InvalidateLayout(); }); public ContentAlignment HorizontalContentAlignment { set { SetValue(HorizontalContentAlignmentProperty, value); } get { return (ContentAlignment)GetValue(HorizontalContentAlignmentProperty); } } public static readonly BindableProperty VerticalContentAlignmentProperty = BindableProperty.Create( "VerticalContentAlignment", typeof(ContentAlignment), typeof(WrapLayout), ContentAlignment.Center, propertyChanged: (bindable, oldvalue, newvalue) => { ((WrapLayout)bindable).InvalidateLayout(); }); public ContentAlignment VerticalContentAlignment { set { SetValue(VerticalContentAlignmentProperty, value); } get { return (ContentAlignment)GetValue(VerticalContentAlignmentProperty); } } protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { Size totalSize = CalculateSize(widthConstraint, heightConstraint); return new SizeRequest(totalSize); } public Size CalculateSize(double widthConstraint, double heightConstraint) { LayoutData layoutData = GetLayoutData(widthConstraint, heightConstraint); if (layoutData.VisibleChildCount == 0) { return new Size(); } Size totalSize = new Size(layoutData.CellSize.Width * layoutData.Columns, layoutData.CellSize.Height * layoutData.Rows); return totalSize; } protected override void LayoutChildren(double x, double y, double width, double height) { LayoutData layoutData = GetLayoutData(width, height); if (layoutData.VisibleChildCount == 0) { return; } double xChild = x; double yChild = y; int row = 0; int column = 0; foreach (View child in Children) { if (!child.IsVisible) { continue; } SizeRequest csr = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity); double xOffSet = HorizontalContentAlignment.Equals(ContentAlignment.Start) ? CellPadding / 2.0F : HorizontalContentAlignment.Equals(ContentAlignment.Center) ? (layoutData.CellSize.Width - csr.Request.Width) / 2.0F : layoutData.CellSize.Width - (csr.Request.Width + (CellPadding / 2.0F)); double yOffSet = VerticalContentAlignment.Equals(ContentAlignment.Start) ? CellPadding / 2.0F : VerticalContentAlignment.Equals(ContentAlignment.Center) ? (layoutData.CellSize.Height - csr.Request.Height) / 2.0F : layoutData.CellSize.Height - (csr.Request.Height + (CellPadding / 2.0F)); LayoutChildIntoBoundingRegion( child, new Rectangle( new Point(xChild + xOffSet, yChild + yOffSet), new Size( layoutData.CellSize.Width - (xOffSet * 2.0F), layoutData.CellSize.Height - (yOffSet * 2.0F)) ) ); if (++column == layoutData.Columns) { column = 0; row++; xChild = x; yChild += layoutData.CellSize.Height; } else { xChild += layoutData.CellSize.Width; } } } LayoutData GetLayoutData(double width, double height) { Size size = new Size(width, height); // Check if cached information is available. if (layoutDataCache.ContainsKey(size)) { return layoutDataCache[size]; } int visibleChildCount = 0; Size maxChildSize = new Size(); int rows = 0; int columns = 0; LayoutData layoutData = new LayoutData(); // Enumerate through all the children. foreach (View child in Children) { // Skip invisible children. if (!child.IsVisible) continue; // Count the visible children. visibleChildCount++; // Get the child's requested size. SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity); // Accumulate the maximum child size. maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width + CellPadding); maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height + CellPadding); } if (visibleChildCount != 0) { // Calculate the number of rows and columns. if (Double.IsPositiveInfinity(width)) { columns = visibleChildCount; rows = 1; } else { columns = (int)(width / maxChildSize.Width); columns = Math.Max(1, columns); rows = (visibleChildCount + columns - 1) / columns; } // Now maximize the cell size based on the layout size. Size cellSize = new Size(); if (Double.IsPositiveInfinity(width)) { cellSize.Width = maxChildSize.Width; } else { cellSize.Width = width / columns; } if (Double.IsPositiveInfinity(height) || VerticalOptions.Alignment.Equals(LayoutAlignment.Start)) { cellSize.Height = maxChildSize.Height; } else { cellSize.Height = height / rows; } layoutData = new LayoutData(visibleChildCount, cellSize, rows, columns); } layoutDataCache.Add(size, layoutData); return layoutData; } protected override void InvalidateLayout() { base.InvalidateLayout(); // Discard all layout information for children added or removed. layoutDataCache.Clear(); } protected override void OnChildMeasureInvalidated() { base.OnChildMeasureInvalidated(); // Discard all layout information for child size changed. layoutDataCache.Clear(); } } }