216 lines
7.2 KiB
C#
216 lines
7.2 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Xamarin.Forms;
|
|
|
|
namespace Billing.UI
|
|
{
|
|
public class WrapLayout : Layout<View>
|
|
{
|
|
public static readonly BindableProperty ItemsSourceProperty = Helper.Create<IList, WrapLayout>(nameof(ItemsSource), propertyChanged: OnItemsSourcePropertyChanged);
|
|
public static readonly BindableProperty ColumnSpacingProperty = Helper.Create<double, WrapLayout>(nameof(ColumnSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout());
|
|
public static readonly BindableProperty RowSpacingProperty = Helper.Create<double, WrapLayout>(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<Size, LayoutData> 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;
|
|
}
|
|
}
|
|
}
|