New UWP Community Toolkit - Staggered panel

概述git

前面 New UWP Community Toolkit 文章中,咱們對 2.2.0 版本的重要更新作了簡單回顧,其中簡單介紹了 Staggered panel,本篇咱們結合代碼詳細講解  Staggered panel 的實現。github

Staggered panel 是一種交錯排列的面板控件,容許面板中的 item 以非整齊排列的方式排列,每一個 item 會被添加到當前佔用空間最小的列。這種排列方式,很是適用於圖片類,新聞資訊類的應用,官方示例展現以下圖:windows

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/staggeredpanelide

Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls佈局

 

開發過程spa

代碼分析code

StaggeredPanel 類繼承自 Panel類,咱們先來看看它的構成:orm

  • 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 中可在調用類中獲取、設置和綁定的兩個依賴屬性:blog

  • 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,調整列數組中對應列的高度;

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!!!

相關文章
相關標籤/搜索