using System; using System.Collections; using System.Collections.Generic; using Xamarin.Forms; namespace Billing.UI { public class WrapLayout : Layout { public static readonly BindableProperty ItemsSourceProperty = Helper.Create(nameof(ItemsSource), propertyChanged: OnItemsSourcePropertyChanged); public static readonly BindableProperty ColumnSpacingProperty = Helper.Create(nameof(ColumnSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout()); public static readonly BindableProperty RowSpacingProperty = Helper.Create(nameof(RowSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout()); private static void OnItemsSourcePropertyChanged(WrapLayout layout, IList old, IList list) { var itemTemplate = BindableLayout.GetItemTemplate(layout); if (itemTemplate == null) { return; } if (list == null) { layout.Children.Clear(); layout.InvalidateLayout(); } else { layout.freezed = true; layout.Children.Clear(); foreach (var item in list) { var child = itemTemplate.CreateContent(); if (child is View view) { view.BindingContext = item; layout.Children.Add(view); } } layout.freezed = false; layout.InvalidateLayout(); } } public IList ItemsSource { get => (IList)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } public double ColumnSpacing { get => (double)GetValue(ColumnSpacingProperty); set => SetValue(ColumnSpacingProperty, value); } public double RowSpacing { get => (double)GetValue(RowSpacingProperty); set => SetValue(RowSpacingProperty, value); } private readonly Dictionary layoutDataCache = new(); private bool freezed; protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { var data = GetLayoutData(widthConstraint, heightConstraint); if (data.VisibleChildrenCount == 0) { return new SizeRequest(); } var totalSize = new Size( data.CellSize.Width * data.Columns + ColumnSpacing * (data.Columns - 1), data.CellSize.Height * data.Rows + RowSpacing * (data.Rows - 1)); return new SizeRequest(totalSize); } protected override void LayoutChildren(double x, double y, double width, double height) { if (freezed) { return; } var data = GetLayoutData(width, height); if (data.VisibleChildrenCount == 0) { return; } double xChild = x; double yChild = y; int row = 0; int column = 0; var columnSpacing = ColumnSpacing; var rowSpacing = RowSpacing; foreach (View child in Children) { if (!child.IsVisible) { continue; } LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), data.CellSize)); if (++column == data.Columns) { column = 0; row++; xChild = x; yChild += rowSpacing + data.CellSize.Height; } else { xChild += columnSpacing + data.CellSize.Width; } } } protected override void InvalidateLayout() { base.InvalidateLayout(); layoutDataCache.Clear(); } protected override void OnChildMeasureInvalidated() { base.OnChildMeasureInvalidated(); layoutDataCache.Clear(); } private LayoutData GetLayoutData(double width, double height) { Size size = new(width, height); if (layoutDataCache.ContainsKey(size)) { return layoutDataCache[size]; } int visibleChildrenCount = 0; Size maxChildSize = new(); LayoutData layoutData; foreach (View child in Children) { if (!child.IsVisible) { continue; } visibleChildrenCount++; SizeRequest childSizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity); maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width); maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height); } if (visibleChildrenCount > 0) { var columnSpacing = ColumnSpacing; int rows; int columns; if (double.IsPositiveInfinity(width)) { columns = visibleChildrenCount; rows = 1; } else { columns = (int)((width + columnSpacing) / (maxChildSize.Width + columnSpacing)); columns = Math.Max(1, columns); rows = (visibleChildrenCount + columns - 1) / columns; } Size cellSize = new(); if (double.IsPositiveInfinity(width)) { cellSize.Width = maxChildSize.Width; } else { cellSize.Width = (width - columnSpacing * (columns - 1)) / columns; } //if (double.IsPositiveInfinity(height)) { cellSize.Height = maxChildSize.Height; } //else //{ // cellSize.Height = (height - RowSpacing * (rows - 1)) / rows; //} layoutData = new LayoutData(visibleChildrenCount, cellSize, rows, columns); } else { layoutData = new LayoutData(); } layoutDataCache.Add(size, layoutData); return layoutData; } } public class LayoutData { public int VisibleChildrenCount { get; set; } public Size CellSize { get; set; } public int Rows { get; set; } public int Columns { get; set; } public LayoutData() { } public LayoutData(int count, Size size, int rows, int columns) { VisibleChildrenCount = count; CellSize = size; Rows = rows; Columns = columns; } } }