using System.Collections; using Xamarin.Forms; namespace Billing.UI { public class GroupStackLayout : Layout { public static readonly BindableProperty GroupHeaderTemplateProperty = BindableProperty.Create(nameof(GroupHeaderTemplate), typeof(DataTemplate), typeof(GroupStackLayout)); public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(GroupStackLayout)); public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(GroupStackLayout), propertyChanged: OnItemsSourcePropertyChanged); public static readonly BindableProperty SpacingProperty = BindableProperty.Create(nameof(Spacing), typeof(double), typeof(GroupStackLayout), defaultValue: 4d); public static readonly BindableProperty RowHeightProperty = BindableProperty.Create(nameof(RowHeight), typeof(double), typeof(GroupStackLayout), defaultValue: 32d); public static readonly BindableProperty GroupHeightProperty = BindableProperty.Create(nameof(GroupHeight), typeof(double), typeof(GroupStackLayout), defaultValue: 24d); public DataTemplate GroupHeaderTemplate { get => (DataTemplate)GetValue(GroupHeaderTemplateProperty); set => SetValue(GroupHeaderTemplateProperty, value); } public DataTemplate ItemTemplate { get => (DataTemplate)GetValue(ItemTemplateProperty); set => SetValue(ItemTemplateProperty, value); } public IList ItemsSource { get => (IList)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } public double Spacing { get => (double)GetValue(SpacingProperty); set => SetValue(SpacingProperty, value); } public double RowHeight { get => (double)GetValue(RowHeightProperty); set => SetValue(RowHeightProperty, value); } public double GroupHeight { get => (double)GetValue(GroupHeightProperty); set => SetValue(GroupHeightProperty, value); } private static void OnItemsSourcePropertyChanged(BindableObject obj, object old, object @new) { var stack = (GroupStackLayout)obj; stack.lastWidth = -1; if (@new == null) { //stack.cachedLayout.Clear(); stack.Children.Clear(); stack.InvalidateLayout(); } else if (@new is IList list) { stack.freezed = true; //stack.cachedLayout.Clear(); stack.Children.Clear(); var groupTemplate = stack.GroupHeaderTemplate; var itemTemplate = stack.ItemTemplate; for (var i = 0; i < list.Count; i++) { var item = list[i]; if (item is IList sublist) { if (groupTemplate != null) { var child = groupTemplate.CreateContent(); if (child is View view) { view.BindingContext = item; stack.Children.Add(view); } } foreach (var it in sublist) { var child = itemTemplate.CreateContent(); if (child is View view) { view.BindingContext = it; stack.Children.Add(view); } } } else { var child = itemTemplate.CreateContent(); if (child is View view) { view.BindingContext = list[i]; stack.Children.Add(view); } } } stack.freezed = false; stack.UpdateChildrenLayout(); stack.InvalidateLayout(); } } //private readonly Dictionary cachedLayout = new(); private bool freezed; private double lastWidth = -1; private SizeRequest lastSizeRequest; public void Refresh(IList list) { OnItemsSourcePropertyChanged(this, null, list); } protected override void LayoutChildren(double x, double y, double width, double height) { if (freezed) { return; } var source = ItemsSource; if (source == null || source.Count <= 0) { return; } var spacing = Spacing; var lastHeight = 0d; var rowHeight = RowHeight; var groupHeight = GroupHeight; foreach (var item in Children) { //var measured = item.Measure(width, height, MeasureFlags.IncludeMargins); //var rect = new Rectangle( // 0, lastHeight, width, // measured.Request.Height); //if (cachedLayout.TryGetValue(item, out var v)) //{ // if (v != rect) // { // cachedLayout[item] = rect; // item.Layout(rect); // } //} //else //{ // cachedLayout.Add(item, rect); // item.Layout(rect); //} double itemHeight; if (item.BindingContext is IList) { itemHeight = groupHeight; } else { itemHeight = rowHeight; } var rect = new Rectangle(0, lastHeight, width, itemHeight); //item.Layout(rect); LayoutChildIntoBoundingRegion(item, rect); lastHeight += itemHeight + spacing; } } protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { if (lastWidth == widthConstraint) { return lastSizeRequest; } lastWidth = widthConstraint; var spacing = Spacing; var lastHeight = 0d; var rowHeight = RowHeight; var groupHeight = GroupHeight; foreach (var item in Children) { //var measured = item.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins); //lastHeight += measured.Request.Height + spacing; if (item.BindingContext is IList) { lastHeight += groupHeight + spacing; } else { lastHeight += rowHeight + spacing; } } lastSizeRequest = new SizeRequest(new Size(widthConstraint, lastHeight)); return lastSizeRequest; } } }