概述 UniformGrid 控制項是一個響應式的佈局控制項,允許把 items 排列在一組均勻分佈的行或列中,以填充整體的可用顯示空間,形成均勻的多個網格。預設情況下,網格中的每個單元格大小相同。 這是一個非常實用的控制項,比如相冊應用中多行多列均勻排列圖片,比如新聞類應用中排列新聞,再比如我們在來畫視 ...
概述
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 控制項的代碼結構:
- TakenSpotsReferenceHolder.cs - 獲取和設置點數組,標識佈局中的 item 是否固定;
- UniformGrid.Helpers.cs - UniformGrid 控制項幫助類,主要處理控制項的行列佈局和排列邏輯;
- UniformGrid.Properties.cs - UniformGrid 控制項的依賴屬性類;
- UniformGrid.cs - 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 控制項所需的依賴屬性,主要有:
- AutoLayout - 獲取和設置自動佈局屬性,包括對行和列的操作;
- Columns - UniformGrid 的列屬性;
- FirstColumn - UniformGrid 的首列屬性,獲取的是首行元素距離第一列的偏移量;
- Orientation - UniformGrid 的排列方式,包括橫向和縱向兩種;
- Rows - UniformGrid 的行屬性;
3. UniformGrid.cs
該類主要是 UnifromGrid 在 Grid 類基礎上的處理,主要處理測量和排列的方法,我們來看一下功能比較複雜的 MeasureOverride() 方法,ArrangeOverride() 方法實現很簡單,這裡不做分析。
1). MeasureOverride()
- 首先根據可見元素集合,獲取控制項的行列數量,設置行列定義;
- 遍歷所有可見元素,根據每個元素的行列和行列跨度屬性,設置自動佈局,填充 spotsTaken;
- 計算行和列的空白空間總數值,再根據總空間數值和行列數,計算出一個元素的尺寸;
- 遍歷所有可見元素,找出元素中最大的寬度和高度;再用這個最大尺寸,乘上行列數,加上空白空間數值,得到控制項所需尺寸;
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 !!!