using System;
using Windows.Foundation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml;
namespace TestProject
{
/// <summary>
/// 활동 피드 레이아웃
/// </summary>
public class ActivityFeedLayout : VirtualizingLayout
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 행 간격 속성 - RowSpacingProperty
/// <summary>
/// 행 간격 속성
/// </summary>
public static readonly DependencyProperty RowSpacingProperty = DependencyProperty.Register
(
"RowSpacing",
typeof(double),
typeof(ActivityFeedLayout),
new PropertyMetadata(0, PropertyChangedCallback)
);
#endregion
#region 열 간격 속성 - ColumnSpacingProperty
/// <summary>
/// 열 간격 속성
/// </summary>
public static readonly DependencyProperty ColumnSpacingProperty = DependencyProperty.Register
(
"ColumnSpacing",
typeof(double),
typeof(ActivityFeedLayout),
new PropertyMetadata(0, PropertyChangedCallback)
);
#endregion
#region 최소 항목 크기 속성 - MinimumItemSizeProperty
/// <summary>
/// 최소 항목 크기 속성
/// </summary>
public static readonly DependencyProperty MinimumItemSizeProperty = DependencyProperty.Register
(
"MinimumItemSize",
typeof(Size),
typeof(ActivityFeedLayout),
new PropertyMetadata(Size.Empty, PropertyChangedCallback)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 행 간격
/// </summary>
private double rowSpacing;
/// <summary>
/// 열 간격
/// </summary>
private double columnSpacing;
/// <summary>
/// 최소 항목 크기
/// </summary>
private Size minimumItemSize = Size.Empty;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 행 간격 - RowSpacing
/// <summary>
/// 행 간격
/// </summary>
public double RowSpacing
{
get
{
return this.rowSpacing;
}
set
{
SetValue(RowSpacingProperty, value);
}
}
#endregion
#region 열 간격 - ColumnSpacing
/// <summary>
/// 열 간격
/// </summary>
public double ColumnSpacing
{
get
{
return this.columnSpacing;
}
set
{
SetValue(ColumnSpacingProperty, value);
}
}
#endregion
#region 최소 항목 크기 - MinimumItemSize
/// <summary>
/// 최소 항목 크기
/// </summary>
public Size MinimumItemSize
{
get
{
return this.minimumItemSize;
}
set
{
SetValue(MinimumItemSizeProperty, value);
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 속성 변경시 콜백 처리하기 - PropertyChangedCallback(d, e)
/// <summary>
/// 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ActivityFeedLayout layout = d as ActivityFeedLayout;
if(e.Property == RowSpacingProperty)
{
layout.rowSpacing = (double)e.NewValue;
}
else if(e.Property == ColumnSpacingProperty)
{
layout.columnSpacing = (double)e.NewValue;
}
else if(e.Property == MinimumItemSizeProperty)
{
layout.minimumItemSize = (Size)e.NewValue;
}
else
{
throw new InvalidOperationException("Don't know what you are talking about!");
}
layout.InvalidateMeasure();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Protected
#region 컨텍스트용 초기화하기 (코어) - InitializeForContextCore(context)
/// <summary>
/// 컨텍스트용 초기화하기 (코어)
/// </summary>
/// <param name="context">가상화 레이아웃 컨텍스트</param>
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
{
base.InitializeForContextCore(context);
if(!(context.LayoutState is ActivityFeedLayoutState))
{
context.LayoutState = new ActivityFeedLayoutState();
}
}
#endregion
#region 컨텍스트용 초기화 취소하기 (코어) - UninitializeForContextCore(context)
/// <summary>
/// 컨텍스트용 초기화 취소하기 (코어)
/// </summary>
/// <param name="context">가상화 레이아웃 컨텍스트</param>
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
{
base.UninitializeForContextCore(context);
context.LayoutState = null;
}
#endregion
#region 측정하기 (오버라이드) - MeasureOverride(context, availableSize)
/// <summary>
/// 측정하기 (오버라이드)
/// </summary>
/// <param name="context">가상화 레이아웃 컨텍스트</param>
/// <param name="availableSize">이용 가능한 크기</param>
/// <returns>측정 크기</returns>
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
{
if(MinimumItemSize == Size.Empty)
{
UIElement firstElement = context.GetOrCreateElementAt(0);
firstElement.Measure(new Size(float.PositiveInfinity, float.PositiveInfinity));
this.minimumItemSize = firstElement.DesiredSize;
}
int firstRowIndex = Math.Max
(
(int)(context.RealizationRect.Y / (MinimumItemSize.Height + RowSpacing)) - 1,
0
);
int lastRowIndex = Math.Min
(
(int)(context.RealizationRect.Bottom / (this.MinimumItemSize.Height + this.RowSpacing)) + 1,
(int)(context.ItemCount / 3)
);
ActivityFeedLayoutState state = context.LayoutState as ActivityFeedLayoutState;
state.LayoutRectList.Clear();
state.FirstRealizedIndex = firstRowIndex * 3;
double desiredItemWidth = Math.Max
(
this.MinimumItemSize.Width,
(availableSize.Width - ColumnSpacing * 3) / 4
);
for(int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++)
{
int firstItemIndex = rowIndex * 3;
Rect[] boundRectangleArrayForCurrentRow = CalculateLayoutBoundRectangleArrayForRow(rowIndex, desiredItemWidth);
for(int columnIndex = 0; columnIndex < 3; columnIndex++)
{
int index = firstItemIndex + columnIndex;
UIElement containerElement = context.GetOrCreateElementAt(index);
containerElement.Measure
(
new Size
(
boundRectangleArrayForCurrentRow[columnIndex].Width,
boundRectangleArrayForCurrentRow[columnIndex].Height
)
);
state.LayoutRectList.Add(boundRectangleArrayForCurrentRow[columnIndex]);
}
}
double extentHeight;
if(context.ItemCount == 0)
{
extentHeight = 0;
}
else
{
extentHeight = ((int)(context.ItemCount / 3) - 1) * (MinimumItemSize.Height + RowSpacing) + MinimumItemSize.Height;
}
return new Size(desiredItemWidth * 4 + this.ColumnSpacing * 2, extentHeight);
}
#endregion
#region 배치하기 (오버라이드) - ArrangeOverride(context, finalSize)
/// <summary>
/// 배치하기 (오버라이드)
/// </summary>
/// <param name="context">가상화 레이아웃 컨텍스트</param>
/// <param name="finalSize">최종 크기</param>
/// <returns>배치 크기</returns>
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
{
ActivityFeedLayoutState state = context.LayoutState as ActivityFeedLayoutState;
VirtualizingLayoutContext virtualContext = context as VirtualizingLayoutContext;
int currentIndex = state.FirstRealizedIndex;
foreach(Rect arrangeRect in state.LayoutRectList)
{
UIElement containerElement = virtualContext.GetOrCreateElementAt(currentIndex);
containerElement.Arrange(arrangeRect);
currentIndex++;
}
return finalSize;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 행을 위한 레이아웃 경계 사각형 배열 계산하기 - CalculateLayoutBoundRectangleArrayForRow(rowIndex, desiredItemWidth)
/// <summary>
/// 행을 위한 레이아웃 경계 사각형 배열 계산하기
/// </summary>
/// <param name="rowIndex">행 인덱스</param>
/// <param name="desiredItemWidth">희망 항목 너비</param>
/// <returns>레이아웃 경계 사각형 배열</returns>
private Rect[] CalculateLayoutBoundRectangleArrayForRow(int rowIndex, double desiredItemWidth)
{
Rect[] boundRectangeArrayForRow = new Rect[3];
double yoffset = rowIndex * (MinimumItemSize.Height + RowSpacing);
boundRectangeArrayForRow[0].Y = boundRectangeArrayForRow[1].Y = boundRectangeArrayForRow[2].Y = yoffset;
boundRectangeArrayForRow[0].Height = boundRectangeArrayForRow[1].Height = boundRectangeArrayForRow[2].Height = MinimumItemSize.Height;
if(rowIndex % 2 == 0)
{
boundRectangeArrayForRow[0].X = 0;
boundRectangeArrayForRow[0].Width = desiredItemWidth;
boundRectangeArrayForRow[1].X = boundRectangeArrayForRow[0].Right + ColumnSpacing;
boundRectangeArrayForRow[1].Width = desiredItemWidth;
boundRectangeArrayForRow[2].X = boundRectangeArrayForRow[1].Right + ColumnSpacing;
boundRectangeArrayForRow[2].Width = desiredItemWidth * 2 + ColumnSpacing;
}
else
{
boundRectangeArrayForRow[0].X = 0;
boundRectangeArrayForRow[0].Width = (desiredItemWidth * 2 + ColumnSpacing);
boundRectangeArrayForRow[1].X = boundRectangeArrayForRow[0].Right + ColumnSpacing;
boundRectangeArrayForRow[1].Width = desiredItemWidth;
boundRectangeArrayForRow[2].X = boundRectangeArrayForRow[1].Right + ColumnSpacing;
boundRectangeArrayForRow[2].Width = desiredItemWidth;
}
return boundRectangeArrayForRow;
}
#endregion
}
}