diff --git a/Billing.Shared/Billing.Shared.projitems b/Billing.Shared/Billing.Shared.projitems index a25db1d..7572722 100644 --- a/Billing.Shared/Billing.Shared.projitems +++ b/Billing.Shared/Billing.Shared.projitems @@ -38,6 +38,7 @@ <Compile Include="$(MSBuildThisFileDirectory)UI\Definition.cs" /> <Compile Include="$(MSBuildThisFileDirectory)UI\GroupStackLayout.cs" /> <Compile Include="$(MSBuildThisFileDirectory)UI\ItemSelectPage.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)UI\WrapLayout.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Views\AccountPage.xaml.cs"> <DependentUpon>AccountPage.xaml</DependentUpon> </Compile> diff --git a/Billing.Shared/UI/WrapLayout.cs b/Billing.Shared/UI/WrapLayout.cs new file mode 100644 index 0000000..20ef995 --- /dev/null +++ b/Billing.Shared/UI/WrapLayout.cs @@ -0,0 +1,216 @@ +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 = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(WrapLayout), propertyChanged: OnItemsSourcePropertyChanged); + public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(nameof(ColumnSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout()); + public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(nameof(RowSpacing), typeof(double), typeof(WrapLayout), defaultValue: 5.0, propertyChanged: (obj, _, _) => ((WrapLayout)obj).InvalidateLayout()); + + private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new) + { + var itemTemplate = BindableLayout.GetItemTemplate(obj); + if (itemTemplate == null) + { + return; + } + var layout = (WrapLayout)obj; + if (@new == null) + { + layout.Children.Clear(); + layout.InvalidateLayout(); + } + else if (@new is IList list) + { + 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; + } + } +}