New UWP Community Toolkit 6.0 - Staggered panel

举报
shaomeng 发表于 2020/02/28 11:30:13 2020/02/28
【摘要】 概述前面 New UWP Community Toolkit 文章中,我们对 6.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解 Staggered panel 的实现。Staggered panel 是一种交错排列的面板控件,允许面板中的 item 以非整齐排列的方式排列,每个 item 会被添加到当前占用空间最小的列。这种排...

概述

前面 New UWP Community Toolkit 文章中,我们对 6.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 更新加入的内容


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,调整列数组中对应列的高度;


  horizontalOffset = verticalOffset = numColumns = ()Math.Floor(finalSize.Width / (HorizontalAlignment ==+= finalSize.Width - (numColumns *  (HorizontalAlignment ==+= (finalSize.Width - (numColumns * _columnWidth)) /  columnHeights =   ( i = ; i < Children.Count; i++ columnIndex = child = elementSize = elementWidth = elementHeight = (elementWidth > differencePercentage = _columnWidth /= elementHeight *==  Rect(horizontalOffset + (_columnWidth * columnIndex), columnHeights[columnIndex] 
                                                ++=


 

最后来看一下前面 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!!!


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。