这里实现了一个横向的简易版 StackPanel
// 1. 计算需要的尺寸
protected override Size MeasureOverride(Size availableSize)
{
var resultSize = new Size(); // 需要的总尺寸
var currentLineSize = new Size(); // 当前行尺寸
foreach (UIElement element in this.InternalChildren)
{
element.Measure(availableSize); // 让子元素计算出需要的尺寸 element.DesiredSize
// 如果新元素添加到当前行会超出当前行的宽度, 则换行
if (currentLineSize.Width + element.DesiredSize.Width > availableSize.Width)
{
// 进行换行计算
AppendNewLine(ref currentLineSize, ref resultSize);
}
// 生成新行
currentLineSize.Width += element.DesiredSize.Width;
currentLineSize.Height = Math.Max(currentLineSize.Height, element.DesiredSize.Height);
}
// 将可能存在的最后一行添加到结果中
AppendNewLine(ref currentLineSize, ref resultSize);
return this.CountResultSize(resultSize, availableSize);
}
// 2. 正式布局, 注意元素是缓存一行,要输出下一行时,一次性输出
protected override Size ArrangeOverride(Size finalSize)
{
var resultSize = new Size(); // 最终于的尺寸
var currentLineSize = new Size(); // 缓存某一行的尺寸, 供该行元素一次性输出
var elements = new List<UIElement>(); // 缓存某一行的元素, 一次性绘出
foreach (UIElement element in this.InternalChildren)
{
// MeasureOverride 中已经计算过子元素需要的尺寸,这里直接添加到当前行,计算要不要换行
if (currentLineSize.Width + element.DesiredSize.Width > finalSize.Width)
{
// 如果要换行,则对当前行的子元素进行输出
ArrangeElements(elements, resultSize.Height, currentLineSize.Height, finalSize.Width);
// 清空缓存的当前行
elements.Clear();
// 进行换行尺寸计算
AppendNewLine(ref currentLineSize, ref resultSize);
}
// 缓存当前行要添加的元素
elements.Add(element);
// 计算当前行长度
currentLineSize.Width += element.DesiredSize.Width;
currentLineSize.Height = Math.Max(currentLineSize.Height, element.DesiredSize.Height);
}
ArrangeElements(elements, resultSize.Height, currentLineSize.Height, finalSize.Width);
AppendNewLine(ref currentLineSize, ref resultSize);
/*return resultSize;*/
return this.CountResultSize(resultSize, finalSize);
/*return finalSize;*/
}
// 进行换行计算, 简单的将当前行的尺寸加到总尺寸中
private static void AppendNewLine(ref Size currentLineSize, ref Size resultSize)
{
resultSize.Height += currentLineSize.Height;
resultSize.Width = Math.Max(resultSize.Width, currentLineSize.Width);
currentLineSize = new Size();
}
// 输出当前行的元素, 按顺序从左到右进行排列输出,给子元素的尺寸要实际计算出剩余的空间
private static void ArrangeElements(List<UIElement> elements, double y, double limitHeight, double limitWidth)
{
var x = 0d;
foreach (var element in elements)
{
var width = Math.Min(limitWidth - x, element.DesiredSize.Width);
var height = Math.Min(limitHeight, element.DesiredSize.Height);
// 因为元素的高度可能不一样,这里将元素上下居中一下。可以直接用 y 值,就是上对齐
var newY = y + (limitHeight - element.DesiredSize.Height) / 2;
element.Arrange(new Rect(x, newY, width, height));
x += element.DesiredSize.Width;
}
}
// 计算最终需要的尺寸。比如计算的尺寸与分配的有效尺寸,哪个小用哪个
private Size CountResultSize(Size resultSize, Size availableSize)
{
// 计算最终的尺寸, 如果大于可用尺寸,哪个小用哪个
var width = resultSize.Width;
var height = resultSize.Height;
if (double.IsFinite(availableSize.Width))
{
width = Math.Min(resultSize.Width, availableSize.Width);
}
if (double.IsFinite(availableSize.Height))
{
height = Math.Min(resultSize.Height, availableSize.Height);
}
return new Size(width, height);
}