概述
UniformGrid 控件是一个响应式的布局控件,允许把 items 排列在一组均匀分布的行或列中,以填充整体的可用显示空间,形成均匀的多个网格。默认情况下,网格中的每个单元格大小相同。
这是一个非常实用的控件,比如相册应用中多行多列均匀排列图片,比如新闻类应用中排列新闻,再比如我们在来画视频中展示用户作品封面和简要信息等,因为它支持响应布局,所以在应用尺寸变化时显示会很友好。
下面是 Windows Community Toolkit Sample App 的示例截图和 code/doc 地址:
Windows Community Toolkit Doc - UniformGrid
Windows Community Toolkit Source Code - UniformGrid
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
开发过程
代码结构分析
首先来看 UniformGrid 控件的代码结构:
UniformGrid 控件的代码实现比较简单,我们来看几个类中重要的方法:
1. UniformGrid.Helpers.cs
1). GetFreeSpot()
获取目前 UniformGrid 控件中可用的点,分为上下和左右两个方向,分别处理行和列的数据;以行为例,遍历每列的所有行,返回是否可用于放置元素的标识;
internal static IEnumerable<(int row, int column)> GetFreeSpot(TakenSpotsReferenceHolder arrayref, int firstcolumn, bool topdown)
{
if (topdown)
{
var rows = arrayref.SpotsTaken.GetLength(0);
// Layout spots from Top-Bottom, Left-Right (right-left handled automatically by Grid with Flow-Direction).
// Effectively transpose the Grid Layout.
for (int c = 0; c < arrayref.SpotsTaken.GetLength(1); c++)
{
int start = (c == 0 && firstcolumn > 0 && firstcolumn < rows) ? firstcolumn : 0;
for (int r = start; r < rows; r++)
{
if (!arrayref.SpotsTaken[r, c])
{
yield return (r, c);
}
}
}
}
else
{
// 省略列处理代码
...
}
}
2). GetDimensions()
获取 UniformGrid 控件在行和列的数值;先计算目前所有 item 所需的格数,分为 row = 0,column = 0 和两个值都为 0 处理,分别计算 row column 的值;如果两个值有一个为 0,则根据不为 0 的值和 item 数量来判断另一个值;如果两个值都为 0,则定义为方形;
internal static (int rows, int columns) GetDimensions(FrameworkElement[] visible, int rows, int cols, int firstColumn)
{
// If a dimension isn't specified, we need to figure out the other one (or both).
if (rows == 0 || cols == 0)
{
// Calculate the size & area of all objects in the grid to know how much space we need.
var count = Math.Max(1, visible.Sum(item => GetRowSpan(item) * GetColumnSpan(item)));
if (rows == 0)
{
if (cols > 0)
{
// Bound check
var first = (firstColumn >= cols || firstColumn < 0) ? 0 : firstColumn;
// If we have columns but no rows, calculate rows based on column offset and number of children.
rows = (count + first + (cols - 1)) / cols;
return (rows, cols);
}
else
{
// Otherwise, determine square layout if both are zero.
var size = (int)Math.Ceiling(Math.Sqrt(count));
// Figure out if firstColumn is in bounds
var first = (firstColumn >= size || firstColumn < 0) ? 0 : firstColumn;
rows = (int)Math.Ceiling(Math.Sqrt(count + first));
return (rows, rows);
}
}
else if (cols == 0)
{
...
}
}
return (rows, cols);
}
3). SetupRowDefinitions()
SetupRowDefinitions() 和 SetupColumnDefinitions() 实现类似,我们看其中一个;先初始化行定义,遍历行列表,如果有行的布局方式不为自动布局,先把这些布局删掉,再重新以自动布局的方式加入到行定义中;这样实现的目标,是保证行布局能对 item 自适应,缩放时可以自动响应;
internal void SetupRowDefinitions(int rows)
{
// Mark initial definitions so we don't erase them.
foreach (var rd in RowDefinitions)
{
if (GetAutoLayout(rd) == null)
{
SetAutoLayout(rd, false);
}
}
// Remove non-autolayout rows we've added and then add them in the right spots.
if (rows != RowDefinitions.Count)
{
for (int r = RowDefinitions.Count - 1; r >= 0; r--)
{
if (GetAutoLayout(RowDefinitions[r]) == true)
{
RowDefinitions.RemoveAt(r);
}
}
for (int r = this.RowDefinitions.Count; r < rows; r++)
{
var rd = new RowDefinition();
SetAutoLayout(rd, true);
this.RowDefinitions.Insert(r, rd);
}
}
}
2. UniformGrid.Properties.cs
该类定义了 UniformGrid 控件所需的依赖属性,主要有:
3. UniformGrid.cs
该类主要是 UnifromGrid 在 Grid 类基础上的处理,主要处理测量和排列的方法,我们来看一下功能比较复杂的 MeasureOverride() 方法,ArrangeOverride() 方法实现很简单,这里不做分析。
1). MeasureOverride()
protected override Size MeasureOverride(Size availableSize)
{
// Get all Visible FrameworkElement Children
var visible = Children.Where(item => item.Visibility != Visibility.Collapsed && item is FrameworkElement).Select(item => item as FrameworkElement).ToArray();
var (rows, columns) = GetDimensions(visible, Rows, Columns, FirstColumn);
SetupRowDefinitions(rows);
SetupColumnDefinitions(columns);
var spotref = new TakenSpotsReferenceHolder(rows, columns);
foreach (var child in visible)
{
var row = GetRow(child);
var col = GetColumn(child);
var rowspan = GetRowSpan(child);
var colspan = GetColumnSpan(child);
if ((row == 0 && col == 0 && GetAutoLayout(child) == null) || GetAutoLayout(child) == true)
{
SetAutoLayout(child, true);
}
else
{
SetAutoLayout(child, false);
spotref.SpotsTaken.Fill(true, row, col, colspan, rowspan); // row, col, width, height
}
}
double columnSpacingSize = 0;
double rowSpacingSize = 0;
if (_hasGridSpacing)
{
columnSpacingSize = ColumnSpacing * (columns - 1);
rowSpacingSize = RowSpacing * (rows - 1);
}
Size childSize = new Size(
(availableSize.Width - columnSpacingSize) / columns,
(availableSize.Height - rowSpacingSize) / rows);
double maxWidth = 0.0;
double maxHeight = 0.0;
var freespots = GetFreeSpot(spotref, FirstColumn, Orientation == Orientation.Vertical).GetEnumerator();
foreach (var child in visible)
{
if (GetAutoLayout(child) == true)
{
if (freespots.MoveNext())
{
var (row, column) = freespots.Current;
SetRow(child, row);
SetColumn(child, column);
var rowspan = GetRowSpan(child);
var colspan = GetColumnSpan(child);
if (rowspan > 1 || colspan > 1)
{
spotref.SpotsTaken.Fill(true, row, column, GetColumnSpan(child), GetRowSpan(child)); // row, col, width, height
}
}
else
{
child.Measure(Size.Empty);
_overflow.Add(child);
continue;
}
}
else if (GetRow(child) < 0 || GetRow(child) >= rows ||
GetColumn(child) < 0 || GetColumn(child) >= columns)
{
child.Measure(Size.Empty);
_overflow.Add(child);
continue;
}
child.Measure(childSize);
maxWidth = Math.Max(child.DesiredSize.Width, maxWidth);
maxHeight = Math.Max(child.DesiredSize.Height, maxHeight);
}
var desiredSize = new Size((maxWidth * (double)columns) + columnSpacingSize, (maxHeight * (double)rows) + rowSpacingSize);
base.MeasureOverride(desiredSize);
return desiredSize;
}
调用示例
UniformGrid 控件的调用非常简单,下面看看 XAML 中的调用:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<controls:UniformGrid
FirstColumn="1"
Orientation="Horizontal"
Rows="0"
Columns="0">
<Border Background="AliceBlue"
Grid.Row="1" Grid.Column="1"
Grid.RowSpan="2"
Grid.ColumnSpan="2"><TextBlock Text="1"/></Border>
<Border Background="Cornsilk"><TextBlock Text="2"/></Border>
<Border Background="DarkSalmon"><TextBlock Text="3"/></Border>
<Border Background="Gainsboro"><TextBlock Text="4"/></Border>
<Border Background="LightBlue"><TextBlock Text="5"/></Border>
<Border Background="MediumAquamarine"><TextBlock Text="6"/></Border>
<Border Background="MistyRose"><TextBlock Text="7"/></Border>
<Border Background="LightCyan"><TextBlock Text="8"/></Border>
<Border Background="Salmon"><TextBlock Text="9"/></Border>
<Border Background="Goldenrod"><TextBlock Text="10"/></Border>
<Border Background="Pink"><TextBlock Text="11"/></Border>
</controls:UniformGrid>
</Page>
总结
到这里我们就把 Windows Community Toolkit 3.0 中的 UniformGrid 的源代码实现过程讲解完成了,希望能对大家更好的理解和使用这个功能有所帮助。
最后,再跟大家安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通过微博关注最新动态。
衷心感谢 WindowsCommunityToolkit 的作者们杰出的工作,感谢每一位贡献者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!