概述

前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解  Staggered panel 的实现。

Staggered panel 是一种交错排列的面板控件,允许面板中的 item 以非整齐排列的方式排列,每个 item 会被添加到当前占用空间最小的列。这种排列方式,非常适用于图片类,新闻资讯类的应用,官方示例展示如下图:

Source: https://github.com/Microsoft/UWPCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls/StaggeredPanel/StaggeredPanel.cs

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 更新加入的内容
  1. public static readonly DependencyProperty DesiredColumnWidthProperty = DependencyProperty.Register(
  2. nameof(DesiredColumnWidth),
  3. typeof(double),
  4. typeof(StaggeredPanel),
  5. new PropertyMetadata(250d, OnDesiredColumnWidthChanged));
  6.  
  7. public static readonly DependencyProperty PaddingProperty = DependencyProperty.Register(
  8. nameof(Padding),
  9. typeof(Thickness),
  10. typeof(StaggeredPanel),
  11. new PropertyMetadata(default(Thickness), OnPaddingChanged));

而这两个依赖属性注册的 On***Changed 如下,获取当前 StaggeredPanel 后,强制触发一次 Measure 的重新计算:

  1. private static void OnDesiredColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  2. {
  3. var panel = (StaggeredPanel)d;
  4. panel.InvalidateMeasure();
  5. }
  6.  
  7. private static void OnPaddingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  8. {
  9. var panel = (StaggeredPanel)d;
  10. panel.InvalidateMeasure();
  11. }

接下来看一下 StaggeredPanel 的类构造方法:

可以看到,构造方法中注册了一个属性变化后的回调事件,针对 Panel.HorizontalAlignmentProperty 的变化,注册了 OnHorizontalAlignmentChanged 方法,这个方法的功能也很简单,就是强制触发一次 Measure 计算。

  1. public StaggeredPanel()
  2. {
  3. RegisterPropertyChangedCallback(Panel.HorizontalAlignmentProperty, OnHorizontalAlignmentChanged);
  4. }
  1. private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
  2. {
  3. InvalidateMeasure();
  4. }

然后来看两个 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 需要的空间大小;

  1. protected override Size MeasureOverride(Size availableSize)
  2. {
  3. availableSize.Width = availableSize.Width - Padding.Left - Padding.Right;
  4. availableSize.Height = availableSize.Height - Padding.Top - Padding.Bottom;
  5.  
  6. _columnWidth = Math.Min(DesiredColumnWidth, availableSize.Width);
  7. int numColumns = (int)Math.Floor(availableSize.Width / _columnWidth);
  8. if (HorizontalAlignment == HorizontalAlignment.Stretch)
  9. {
  10. _columnWidth = availableSize.Width / numColumns;
  11. }
  12.  
  13. var columnHeights = new double[numColumns];
  14.  
  15. ; i < Children.Count; i++)
  16. {
  17. var columnIndex = GetColumnIndex(columnHeights);
  18.  
  19. var child = Children[i];
  20. child.Measure(new Size(_columnWidth, availableSize.Height));
  21. var elementSize = child.DesiredSize;
  22. columnHeights[columnIndex] += elementSize.Height;
  23. }
  24.  
  25. double desiredHeight = columnHeights.Max();
  26.  
  27. return new Size(availableSize.Width, desiredHeight);
  28. }

ArrangeOverride(finalSize):

该方法作用是根据 Measure 方法计算的最终尺寸,实际去排列 Item,排列完成后给出元素实际占用的尺寸,来看一下具体实现过程:

1. 计算列数,根据 panel 横向对齐方式,在居中和靠右时,重新设置横向偏移值,考虑最终宽度和实际元素宽度的偏差;

2. 遍历 panel 的 children,在排列时对 child 宽度做矫正,如果 child 宽度大于列宽,则把宽度调整到列宽,根据宽高比调整高度;

3. 排列后,重新计算当前占用空间的 bounds,调整列数组中对应列的高度;

  1. protected override Size ArrangeOverride(Size finalSize)
  2. {
  3. double horizontalOffset = Padding.Left;
  4. double verticalOffset = Padding.Top;
  5. int numColumns = (int)Math.Floor(finalSize.Width / _columnWidth);
  6. if (HorizontalAlignment == HorizontalAlignment.Right)
  7. {
  8. horizontalOffset += finalSize.Width - (numColumns * _columnWidth);
  9. }
  10. else if (HorizontalAlignment == HorizontalAlignment.Center)
  11. {
  12. horizontalOffset += (finalSize.Width - (numColumns * _columnWidth)) / ;
  13. }
  14.  
  15. var columnHeights = new double[numColumns];
  16.  
  17. ; i < Children.Count; i++)
  18. {
  19. var columnIndex = GetColumnIndex(columnHeights);
  20.  
  21. var child = Children[i];
  22. var elementSize = child.DesiredSize;
  23.  
  24. double elementWidth = elementSize.Width;
  25. double elementHeight = elementSize.Height;
  26. if (elementWidth > _columnWidth)
  27. {
  28. double differencePercentage = _columnWidth / elementWidth;
  29. elementHeight = elementHeight * differencePercentage;
  30. elementWidth = _columnWidth;
  31. }
  32.  
  33. Rect bounds = new Rect(horizontalOffset + (_columnWidth * columnIndex), columnHeights[columnIndex]
    + verticalOffset, elementWidth, elementHeight);
  34. child.Arrange(bounds);
  35.  
  36. columnHeights[columnIndex] += elementSize.Height;
  37. }
  38.  
  39. return base.ArrangeOverride(finalSize);
  40. }

最后来看一下前面 MeasureOverride 和 ArrangeOverride 方法中都用到的 GetColumnIndex(columnHeights) 方法:

这个方法的作用是根据传入的列高度数组,计算当前高度最小的列索引;这也是 StaggeredPanel 可以实现每次添加到最小高度列的关键方法;

  1. private int GetColumnIndex(double[] columnHeights)
  2. {
  3. ;
  4. ];
  5. ; j < columnHeights.Length; j++)
  6. {
  7. if (columnHeights[j] < height)
  8. {
  9. columnIndex = j;
  10. height = columnHeights[j];
  11. }
  12. }
  13.  
  14. return columnIndex;
  15. }

调用示例

下面示例中,我们使用了 GridView 控件,用 StaggeredPanel 作为 ItemsPanelTemplate;上面说到了两个依赖属性,我们分别作了设置,从下面的运行图中也可以体现出来。大家也可以看到,StaggeredPanel 中 child 的排列规则,确实是按照每个列高度最小的列来排列;而在 panel 宽度变化时,也对应作了重新的计算和排列。

  1. <GridView.ItemTemplate>
  2. <DataTemplate>
  3. <Grid>
  4. <Grid.Background>
  5. <SolidColorBrush Color="{Binding Color}"/>
  6. </Grid.Background>
  7. <Image Source="{Binding Thumbnail}" Stretch="Uniform"/>
  8. <Border Background="#44000000" VerticalAlignment="Top">
  9. <TextBlock Foreground="White" Margin="5,3">
  10. <Run Text="{Binding Title}"/>
  11. </TextBlock>
  12. </Border>
  13. </Grid>
  14. </DataTemplate>
  15. </GridView.ItemTemplate>
  16. <GridView.ItemsPanel>
  17. <ItemsPanelTemplate>
  18. <controls:StaggeredPanel DesiredColumnWidth="135" Padding="25,25,25,25"
  19. HorizontalAlignment="Stretch"/>
  20. </ItemsPanelTemplate>
  21. </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的更多相关文章

  1. New UWP Community Toolkit

    概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...

  2. New UWP Community Toolkit - Carousel

    概述 New UWP Community Toolkit  V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解  Carousel 的实现. Carousel 是 ...

  3. New UWP Community Toolkit - AdaptiveGridView

    概述 UWP Community Toolkit  中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解  AdaptiveGridView 的实现 ...

  4. New UWP Community Toolkit - XAML Brushes

    概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...

  5. New UWP Community Toolkit - Markdown

    概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...

  6. New UWP Community Toolkit - RadialProgressBar

    概述 UWP Community Toolkit  中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解  RadialProgressBar 的实现. Radi ...

  7. New UWP Community Toolkit - RadialGauge

    概述 New UWP Community Toolkit  V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解  RadialGauge 的实现. Radi ...

  8. New UWP Community Toolkit - RangeSelector

    概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 Ra ...

  9. New UWP Community Toolkit - ImageEx

    概述 UWP Community Toolkit  中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解  ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...

随机推荐

  1. jquery获取选中的文本和值

    jquery获取选中的文本和值 1.说明 (1)获取select下拉框选中的索引       $("#selection").get(0).selectedIndex; (2)获取 ...

  2. Struts 有哪些常用标签库

    Struts 有哪些常用标签库 1.html标签库 2.bean标签库 3.logic标签库

  3. Scrapyd日志输出优化

    现在维护着一个新浪微博爬虫,爬取量已经5亿+,使用了Scrapyd部署分布式. Scrapyd运行时会输出日志到本地,导致日志文件会越来越大,这个其实就是Scrapy控制台的输出.但是这个日志其实有用 ...

  4. 如果没有Build path怎么办 .project文件的修改

    <?xml version="1.0" encoding="UTF-8"?><projectDescription> <name& ...

  5. 异常-----Java compiler level does not match解决方法

    1, 先设置好jdk,需要确定 项目,eclipse/myeclipse,系统 用的是同一个版本的JDK,我系统中安装的JDK是1.7,所以我把eclipse的jdk成1.7 2, 进入 window ...

  6. link-cut-tree 简单介绍

    link-cut-tree 简单介绍 前言:这个算法似乎机房全都会,就我不会了TAT...强行搞了很久,勉强照着别人代码抄了一遍qwq 这个本人看论文实在看不懂,太菜了啊!!! 只好直接看如何实现.. ...

  7. xctf的一道题目(77777)

    这次比赛我没有参加,这是结束之后才做的题目 题目链接http://47.97.168.223:23333 根据题目信息,我们要update那个points值,那就是有很大可能这道题目是一个sql注入的 ...

  8. 一个10年Java程序员的年终总结,献给还在迷茫中的你

    我越来越担心我作为一个Java程序员的未来. 恍然间,发现自己在这个行业里已经摸爬滚打将近10年了,原以为自己就凭已有的项目经验和工作经历怎么着也应该算得上是一个业内比较资历的人士了,但是今年在换工作 ...

  9. Spring【AOP模块】就是这么简单

    前言 到目前为止,已经简单学习了Spring的Core模块.....于是我们就开启了Spring的AOP模块了...在讲解AOP模块之前,首先我们来讲解一下cglib代理.以及怎么手动实现AOP编程 ...

  10. python数据类型——列表和元组类型

    列表类型(list) 定义一个列表类型很简单: l = ['a','b','c','d','e','f'] 变量l即为列表类型,可以用type方法查看: print(type(l)) 列表的增删改查 ...