New UWP Community Toolkit - Staggered panel
概述
前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解 Staggered panel 的实现。
Staggered panel 是一种交错排列的面板控件,允许面板中的 item 以非整齐排列的方式排列,每个 item 会被添加到当前占用空间最小的列。这种排列方式,非常适用于图片类,新闻资讯类的应用,官方示例展示如下图:

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/staggeredpanel
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
开发过程
代码分析
StaggeredPanel 类继承自 Panel类,我们先来看看它的构成:
- public static 依赖属性:PaddingProperty, DesiredColumnWidthProperty
- public 变量:Padding, DesiredColumnWidth
- private 变量:_columnWidth
- public 方法:StaggeredPanel()
- protected override 方法:MeasureOverride(availableSize), ArrangeOverride(finalSize)
- private 方法:GetColumnIndex(columnHeights), OnHorizontalAlignmentChanged(sender, dp)
- private static 方法:OnDesiredColumnWidthChanged(d, e), OnPaddingChanged(d, e)

我们先来看一下 StaggeredPanel 中可在调用类中获取、设置和绑定的两个依赖属性:
- DesiredColumnWidth - 获取和设置 StaggeredPanel 内 Item 期望列宽度的属性,默认值宽度是 250d;
- Padding - 获取和设置 StaggeredPanel 内 Item padding 属性,默认值是 Thickness 的默认值 (0,0,0,0),它也是本次 V2.2.0 更新加入的内容
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];
; 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)) / ;
}
var columnHeights = new double[numColumns];
; 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)
{
;
];
; 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!!!
New UWP Community Toolkit - Staggered panel的更多相关文章
- New UWP Community Toolkit
概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...
- New UWP Community Toolkit - Carousel
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解 Carousel 的实现. Carousel 是 ...
- New UWP Community Toolkit - AdaptiveGridView
概述 UWP Community Toolkit 中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解 AdaptiveGridView 的实现 ...
- New UWP Community Toolkit - XAML Brushes
概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...
- New UWP Community Toolkit - Markdown
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...
- New UWP Community Toolkit - RadialProgressBar
概述 UWP Community Toolkit 中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解 RadialProgressBar 的实现. Radi ...
- New UWP Community Toolkit - RadialGauge
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解 RadialGauge 的实现. Radi ...
- New UWP Community Toolkit - RangeSelector
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 Ra ...
- New UWP Community Toolkit - ImageEx
概述 UWP Community Toolkit 中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解 ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...
随机推荐
- jquery Dialog弹框插件使用
var dialog = new Dialog({ title: '购物车', type: 'url', width: 520, content: "Uplolo.aspx", s ...
- 让微信,qq,uc浏览器使用全屏模式,全屏模式里,浏览器是不会上下左右滑动出现背景的
<meta name="x5-fullscreen" content="true"> <meta name="full-screen ...
- Java开发速度神器Lombok,Eclipse端安装使用教程
一.Lombok简介 Lombok是一个代码生成器,可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法. 使用 ...
- spring拦截器的简单实现Interceptor
原文链接:http://lixuanbin.iteye.com/blog/2250100 1. 需求描述 某内部管理系统采用Spring MVC搭建,用户可以登录系统进行CRUD以及其他的一些日常管理 ...
- 【ZJOI2008】树的统计(树链剖分)
题面 Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II ...
- 15.MySQL(三)
索引类型 先创建表 mysql> CREATE TABLE test( -> id INT, -> username VARCHAR(16), -> city VARCHAR( ...
- Java实现mongodb原生增删改查语句
Java实现mongodb原生增删改查语句 2018-03-16 自动化测试时,需校验数据库数据,为了快速自动化,在代码中用原生增删改查语句操作mongodb 结构 代码 0 pom.xml < ...
- 深度剖析PHP序列化和反序列化
序列化 序列化格式 在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构. 序列化函数原型如下: string serialize ( mixed $value ) 先看下面 ...
- SDP(13): Scala.Future - far from completion,绝不能用来做甩手掌柜
在前面几篇关于数据库引擎的讨论里很多的运算函数都返回了scala.Future类型的结果,因为我以为这样就可以很方便的实现了non-blocking效果.无论任何复杂的数据处理操作,只要把它们包在一个 ...
- Struts2 学习之小白开始
Struts2 基础知识学习总结 Struts2 概述:Struts2 是一个用来开发 MVC 应用程序的框架,他提供了 Web 应用程序开发过程中的一些常见问题的解决方案,比如对于用户输入信息合法性 ...