概述git
前面 New UWP Community Toolkit 文章中,咱们对 2.2.0 版本的重要更新作了简单回顾,其中简单介绍了 Staggered panel,本篇咱们结合代码详细讲解 Staggered panel 的实现。github
Staggered panel 是一种交错排列的面板控件,容许面板中的 item 以非整齐排列的方式排列,每一个 item 会被添加到当前占用空间最小的列。这种排列方式,很是适用于图片类,新闻资讯类的应用,官方示例展现以下图:windows
Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/staggeredpanelide
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls; 布局
开发过程spa
代码分析code
StaggeredPanel 类继承自 Panel类,咱们先来看看它的构成:orm
咱们先来看一下 StaggeredPanel 中可在调用类中获取、设置和绑定的两个依赖属性:blog
public static readonly DependencyProperty DesiredColumnWidthProperty = DependencyProperty.Register( nameof(DesiredColumnWidth), typeof(double), typeof(StaggeredPanel), new PropertyMetadata(250d, OnDesiredColumnWidthChanged)); public static readonly DependencyProperty PaddingProperty = DependencyProperty.Register( nameof(Padding), typeof(Thickness), typeof(StaggeredPanel), new PropertyMetadata(default(Thickness), OnPaddingChanged));
而这两个依赖属性注册的 On***Changed 以下,获取当前 StaggeredPanel 后,强制触发一次 Measure 的从新计算:
private static void OnDesiredColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var panel = (StaggeredPanel)d; panel.InvalidateMeasure(); } private static void OnPaddingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var panel = (StaggeredPanel)d; panel.InvalidateMeasure(); }
接下来看一下 StaggeredPanel 的类构造方法:
能够看到,构造方法中注册了一个属性变化后的回调事件,针对 Panel.HorizontalAlignmentProperty 的变化,注册了 OnHorizontalAlignmentChanged 方法,这个方法的功能也很简单,就是强制触发一次 Measure 计算。
public StaggeredPanel() { RegisterPropertyChangedCallback(Panel.HorizontalAlignmentProperty, OnHorizontalAlignmentChanged); }
private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp) { InvalidateMeasure(); }
而后来看两个 override 方法:MeasureOverride(availableSize) 和 ArrangeOverride(finalSize)
MeasureOverride(availableSize) :
该方法做用是传入可用的尺寸,基于其对子元素大小的计算肯定它在布局期间所须要的尺寸,咱们来看一下具体实现过程:
1. 根据 availableSize,去掉 Padding 对应方向的值,得到新的 availableSize,也就是子元素可用的尺寸;
2. 在指望列宽和可用宽度间得到正确的列宽,根据列宽计算当前布局中可用的列数;若是当前控件的横向对齐方式对拉伸,从新设置列宽,这时列宽实际就是指望列宽度;
3. 遍历 panel 中的 children,根据 GetColumnIndex(columnHeights) 方法传回指定 child 的列索引,计算原则是找到 columnHeights 数组中最小值,返回索引;根据返回的索引,把对应 child 的高度加到 columnHeights 对应索引中,更新 columnHeights 数组中每列的总高度值;
4. 在 columnHeights 数组中 ,找到最大值,返回新的尺寸:宽度为可用尺寸的宽度,高度为列数组的最大值;能够看出,这个尺寸就是根据子元素计算出的 panel 须要的空间大小;
protected override Size MeasureOverride(Size availableSize) { availableSize.Width = availableSize.Width - Padding.Left - Padding.Right; availableSize.Height = availableSize.Height - Padding.Top - Padding.Bottom; _columnWidth = Math.Min(DesiredColumnWidth, availableSize.Width); int numColumns = (int)Math.Floor(availableSize.Width / _columnWidth); if (HorizontalAlignment == HorizontalAlignment.Stretch) { _columnWidth = availableSize.Width / numColumns; } var columnHeights = new double[numColumns]; for (int i = 0; i < Children.Count; i++) { var columnIndex = GetColumnIndex(columnHeights); var child = Children[i]; child.Measure(new Size(_columnWidth, availableSize.Height)); var elementSize = child.DesiredSize; columnHeights[columnIndex] += elementSize.Height; } double desiredHeight = columnHeights.Max(); return new Size(availableSize.Width, desiredHeight); }
ArrangeOverride(finalSize):
该方法做用是根据 Measure 方法计算的最终尺寸,实际去排列 Item,排列完成后给出元素实际占用的尺寸,来看一下具体实现过程:
1. 计算列数,根据 panel 横向对齐方式,在居中和靠右时,从新设置横向偏移值,考虑最终宽度和实际元素宽度的误差;
2. 遍历 panel 的 children,在排列时对 child 宽度作矫正,若是 child 宽度大于列宽,则把宽度调整到列宽,根据宽高比调整高度;
3. 排列后,从新计算当前占用空间的 bounds,调整列数组中对应列的高度;
protected override Size ArrangeOverride(Size finalSize) { double horizontalOffset = Padding.Left; double verticalOffset = Padding.Top; int numColumns = (int)Math.Floor(finalSize.Width / _columnWidth); if (HorizontalAlignment == HorizontalAlignment.Right) { horizontalOffset += finalSize.Width - (numColumns * _columnWidth); } else if (HorizontalAlignment == HorizontalAlignment.Center) { horizontalOffset += (finalSize.Width - (numColumns * _columnWidth)) / 2; } var columnHeights = new double[numColumns]; for (int i = 0; i < Children.Count; i++) { var columnIndex = GetColumnIndex(columnHeights); var child = Children[i]; var elementSize = child.DesiredSize; double elementWidth = elementSize.Width; double elementHeight = elementSize.Height; if (elementWidth > _columnWidth) { double differencePercentage = _columnWidth / elementWidth; elementHeight = elementHeight * differencePercentage; elementWidth = _columnWidth; } Rect bounds = new Rect(horizontalOffset + (_columnWidth * columnIndex), columnHeights[columnIndex]
+ verticalOffset, elementWidth, elementHeight); child.Arrange(bounds); columnHeights[columnIndex] += elementSize.Height; } return base.ArrangeOverride(finalSize); }
最后来看一下前面 MeasureOverride 和 ArrangeOverride 方法中都用到的 GetColumnIndex(columnHeights) 方法:
这个方法的做用是根据传入的列高度数组,计算当前高度最小的列索引;这也是 StaggeredPanel 能够实现每次添加到最小高度列的关键方法;
private int GetColumnIndex(double[] columnHeights) { int columnIndex = 0; double height = columnHeights[0]; for (int j = 1; j < columnHeights.Length; j++) { if (columnHeights[j] < height) { columnIndex = j; height = columnHeights[j]; } } return columnIndex; }
调用示例
下面示例中,咱们使用了 GridView 控件,用 StaggeredPanel 做为 ItemsPanelTemplate;上面说到了两个依赖属性,咱们分别做了设置,从下面的运行图中也能够体现出来。你们也能够看到,StaggeredPanel 中 child 的排列规则,确实是按照每一个列高度最小的列来排列;而在 panel 宽度变化时,也对应做了从新的计算和排列。
<GridView.ItemTemplate> <DataTemplate> <Grid> <Grid.Background> <SolidColorBrush Color="{Binding Color}"/> </Grid.Background> <Image Source="{Binding Thumbnail}" Stretch="Uniform"/> <Border Background="#44000000" VerticalAlignment="Top"> <TextBlock Foreground="White" Margin="5,3"> <Run Text="{Binding Title}"/> </TextBlock> </Border> </Grid> </DataTemplate> </GridView.ItemTemplate> <GridView.ItemsPanel> <ItemsPanelTemplate> <controls:StaggeredPanel DesiredColumnWidth="135" Padding="25,25,25,25" HorizontalAlignment="Stretch"/> </ItemsPanelTemplate> </GridView.ItemsPanel>
总结
到这里咱们就把 UWP Community Toolkit 中的 StaggeredPanel 功能的源代码实现过程和简单的调用示例讲解完成了,但愿能对你们更好的理解和使用这个控件有所帮助,也但愿能启发你们去作出更丰富排列规则的 Panel 控件。欢迎你们多多交流,谢谢!
最后,再跟你们安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 你们能够经过微博关注最新动态。
衷心感谢 UWPCommunityToolkit 的做者们杰出的工做,Thank you so much, UWPCommunityToolkit authors!!!