270 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using Xamarin.Forms;
 | |
| 
 | |
| namespace Gallery.UI
 | |
| {
 | |
|     public class FlowLayout : Layout<View>
 | |
|     {
 | |
|         public static readonly BindableProperty ColumnProperty = BindableProperty.Create(
 | |
|             nameof(Column), typeof(int), typeof(FlowLayout), 2, propertyChanged: OnColumnPropertyChanged);
 | |
|         public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(
 | |
|             nameof(RowSpacing), typeof(double), typeof(FlowLayout), 10.0);
 | |
|         public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
 | |
|             nameof(ColumnSpacing), typeof(double), typeof(FlowLayout), 10.0);
 | |
| 
 | |
|         private static void OnColumnPropertyChanged(BindableObject obj, object oldValue, object newValue)
 | |
|         {
 | |
|             var flowLayout = (FlowLayout)obj;
 | |
|             if (oldValue is int column && column != flowLayout.Column)
 | |
|             {
 | |
|                 flowLayout.UpdateChildrenLayout();
 | |
|                 flowLayout.InvalidateLayout();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public int Column
 | |
|         {
 | |
|             get => (int)GetValue(ColumnProperty);
 | |
|             set => SetValue(ColumnProperty, value);
 | |
|         }
 | |
|         public double RowSpacing
 | |
|         {
 | |
|             get => (double)GetValue(RowSpacingProperty);
 | |
|             set => SetValue(RowSpacingProperty, value);
 | |
|         }
 | |
|         public double ColumnSpacing
 | |
|         {
 | |
|             get => (double)GetValue(ColumnSpacingProperty);
 | |
|             set => SetValue(ColumnSpacingProperty, value);
 | |
|         }
 | |
| 
 | |
|         public event EventHandler<HeightEventArgs> MaxHeightChanged;
 | |
| 
 | |
|         public double ColumnWidth { get; private set; }
 | |
| 
 | |
|         private bool freezed;
 | |
|         private double maximumHeight;
 | |
|         private readonly Dictionary<View, Rectangle> cachedLayout = new Dictionary<View, Rectangle>();
 | |
| 
 | |
|         protected override void LayoutChildren(double x, double y, double width, double height)
 | |
|         {
 | |
|             if (freezed)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
|             var column = Column;
 | |
|             if (column <= 0)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
|             var source = ItemsSource;
 | |
|             if (source == null || source.Count <= 0)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
|             var columnSpacing = ColumnSpacing;
 | |
|             var rowSpacing = RowSpacing;
 | |
| 
 | |
|             var columnHeights = new double[column];
 | |
|             var columnSpacingTotal = columnSpacing * (column - 1);
 | |
|             var columnWidth = (width - columnSpacingTotal) / column;
 | |
|             ColumnWidth = columnWidth;
 | |
| 
 | |
|             foreach (var item in Children)
 | |
|             {
 | |
|                 var measured = item.Measure(columnWidth, height, MeasureFlags.IncludeMargins);
 | |
|                 var col = 0;
 | |
|                 for (var i = 1; i < column; i++)
 | |
|                 {
 | |
|                     if (columnHeights[i] < columnHeights[col])
 | |
|                     {
 | |
|                         col = i;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 var rect = new Rectangle(
 | |
|                     col * (columnWidth + columnSpacing),
 | |
|                     columnHeights[col],
 | |
|                     columnWidth,
 | |
|                     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);
 | |
|                 }
 | |
|                 columnHeights[col] += measured.Request.Height + rowSpacing;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private double lastWidth = -1;
 | |
|         private SizeRequest lastSizeRequest;
 | |
| 
 | |
|         protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
 | |
|         {
 | |
|             var column = Column;
 | |
|             if (column <= 0)
 | |
|             {
 | |
|                 return base.OnMeasure(widthConstraint, heightConstraint);
 | |
|             }
 | |
|             if (lastWidth == widthConstraint)
 | |
|             {
 | |
|                 return lastSizeRequest;
 | |
|             }
 | |
|             lastWidth = widthConstraint;
 | |
|             var columnSpacing = ColumnSpacing;
 | |
|             var rowSpacing = RowSpacing;
 | |
| 
 | |
|             var columnHeights = new double[column];
 | |
|             var columnSpacingTotal = columnSpacing * (column - 1);
 | |
|             var columnWidth = (widthConstraint - columnSpacingTotal) / column;
 | |
|             ColumnWidth = columnWidth;
 | |
| 
 | |
|             foreach (var item in Children)
 | |
|             {
 | |
|                 var measured = item.Measure(columnWidth, heightConstraint, MeasureFlags.IncludeMargins);
 | |
|                 var col = 0;
 | |
|                 for (var i = 1; i < column; i++)
 | |
|                 {
 | |
|                     if (columnHeights[i] < columnHeights[col])
 | |
|                     {
 | |
|                         col = i;
 | |
|                     }
 | |
|                 }
 | |
|                 columnHeights[col] += measured.Request.Height + rowSpacing;
 | |
|             }
 | |
|             maximumHeight = columnHeights.Max();
 | |
| 
 | |
|             if (maximumHeight > 0)
 | |
|             {
 | |
|                 MaxHeightChanged?.Invoke(this, new HeightEventArgs { ContentHeight = maximumHeight });
 | |
|             }
 | |
| 
 | |
|             lastSizeRequest = new SizeRequest(new Size(widthConstraint, maximumHeight));
 | |
|             return lastSizeRequest;
 | |
|         }
 | |
| 
 | |
|         public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
 | |
|             nameof(ItemTemplate), typeof(DataTemplate), typeof(FlowLayout));
 | |
|         public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
 | |
|             nameof(ItemsSource), typeof(IList), typeof(FlowLayout), propertyChanged: OnItemsSourcePropertyChanged);
 | |
| 
 | |
|         public DataTemplate ItemTemplate
 | |
|         {
 | |
|             get => (DataTemplate)GetValue(ItemTemplateProperty);
 | |
|             set => SetValue(ItemTemplateProperty, value);
 | |
|         }
 | |
|         public IList ItemsSource
 | |
|         {
 | |
|             get => (IList)GetValue(ItemsSourceProperty);
 | |
|             set => SetValue(ItemsSourceProperty, value);
 | |
|         }
 | |
| 
 | |
|         private static void OnItemsSourcePropertyChanged(BindableObject obj, object oldValue, object newValue)
 | |
|         {
 | |
|             var flowLayout = (FlowLayout)obj;
 | |
|             if (oldValue is IIllustCollectionChanged oldNotify)
 | |
|             {
 | |
|                 oldNotify.CollectionChanged -= flowLayout.OnCollectionChanged;
 | |
|             }
 | |
|             flowLayout.lastWidth = -1;
 | |
|             if (newValue == null)
 | |
|             {
 | |
|                 flowLayout.cachedLayout.Clear();
 | |
|                 flowLayout.Children.Clear();
 | |
|                 flowLayout.InvalidateLayout();
 | |
|             }
 | |
|             else if (newValue is IList newList)
 | |
|             {
 | |
|                 flowLayout.freezed = true;
 | |
|                 flowLayout.cachedLayout.Clear();
 | |
|                 flowLayout.Children.Clear();
 | |
|                 for (var i = 0; i < newList.Count; i++)
 | |
|                 {
 | |
|                     var child = flowLayout.ItemTemplate.CreateContent();
 | |
|                     if (child is View view)
 | |
|                     {
 | |
|                         view.BindingContext = newList[i];
 | |
|                         flowLayout.Children.Add(view);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (newValue is IIllustCollectionChanged newNotify)
 | |
|                 {
 | |
|                     newNotify.CollectionChanged += flowLayout.OnCollectionChanged;
 | |
|                 }
 | |
|                 flowLayout.freezed = false;
 | |
| 
 | |
|                 flowLayout.UpdateChildrenLayout();
 | |
|                 flowLayout.InvalidateLayout();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void OnCollectionChanged(object sender, CollectionChangedEventArgs e)
 | |
|         {
 | |
|             lastWidth = -1;
 | |
|             if (e.OldItems != null)
 | |
|             {
 | |
|                 freezed = true;
 | |
|                 cachedLayout.Clear();
 | |
|                 var index = e.OldStartingIndex;
 | |
|                 for (var i = index + e.OldItems.Count - 1; i >= index; i--)
 | |
|                 {
 | |
|                     Children.RemoveAt(i);
 | |
|                 }
 | |
|                 freezed = false;
 | |
|                 UpdateChildrenLayout();
 | |
|                 InvalidateLayout();
 | |
|             }
 | |
| 
 | |
|             if (e.NewItems == null)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             freezed = true;
 | |
|             var start = e.NewStartingIndex;
 | |
|             for (var i = 0; i < e.NewItems.Count; i++)
 | |
|             {
 | |
|                 var child = ItemTemplate.CreateContent();
 | |
|                 if (child is View view)
 | |
|                 {
 | |
|                     view.BindingContext = e.NewItems[i];
 | |
|                     Children.Insert(start + i, view);
 | |
|                 }
 | |
|             }
 | |
|             freezed = false;
 | |
|             UpdateChildrenLayout();
 | |
|             //InvalidateLayout();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public interface IIllustCollectionChanged
 | |
|     {
 | |
|         event EventHandler<CollectionChangedEventArgs> CollectionChanged;
 | |
|     }
 | |
| 
 | |
|     public class CollectionChangedEventArgs : EventArgs
 | |
|     {
 | |
|         public int OldStartingIndex { get; set; }
 | |
|         public IList OldItems { get; set; }
 | |
|         public int NewStartingIndex { get; set; }
 | |
|         public IList NewItems { get; set; }
 | |
|     }
 | |
| 
 | |
|     public class HeightEventArgs : EventArgs
 | |
|     {
 | |
|         public double ContentHeight { get; set; }
 | |
|     }
 | |
| }
 |