WrapLayout
This commit is contained in:
		
							
								
								
									
										216
									
								
								Billing.Shared/UI/WrapLayout.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								Billing.Shared/UI/WrapLayout.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user