Billing/Billing.Shared/UI/WrapLayout.cs

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;
}
}
}