New UWP Community Toolkit - Staggered panel

来源:https://www.cnblogs.com/shaomeng/archive/2018/03/31/8676696.html
-Advertisement-
Play Games

概述 前面 New UWP Community Toolkit 文章中,我們對 2.2.0 版本的重要更新做了簡單回顧,其中簡單介紹了 Staggered panel,本篇我們結合代碼詳細講解 Staggered panel 的實現。 Staggered panel 是一種交錯排列的面板控制項,允許面 ...


概述

前面 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 更新加入的內容
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!!!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 資料庫遷移方式:PMC(程式包管理控制器),CLI(程式所在目錄控制台操作) 1:在遷移資料庫之前AppSetting.json中配置資料庫信息 註:在NuGet包管理器上同時引入Entityframeworkcore.Tools 和 Entityframeworkcore.sqlserver 插件 ...
  • 本文告訴大家如何使用 win2d 給圖片加上水印。 <! more <! 標簽:水印,win2d,uwp 安裝 首先需要使用 Nuget 安裝 win2d ,安裝參見 "win10 uwp win2d" 如果沒有更新 dot net core 那麼在運行可能會出現下麵異常 那麼直接更新 dot ne ...
  • 概述 New UWP Community Toolkit V2.2.0 的版本發佈日誌中提到了 Carousel 的調整,本篇我們結合代碼詳細講解 Carousel 的實現。 Carousel 是一種傳送帶形態的控制項,在圖片展示類的應用中有非常多的應用,它擁有很好的流暢度,可以做很多的自定義,並集成 ...
  • 本文告訴大家一個簡單的方法從 BBcode 轉為 Markdown ...
  • 本文告訴大家一個特殊的做法,可以修改一個字元串常量 <! more 我們來寫一個簡單的程式,把一個常量字元串輸出 其中的 Foo 是其他的函數,大家可以猜到輸出是 lindexi ,但是,實際上把Foo調用函數添加之後,輸出是 Lindexi 被大寫了。那麼這時 Foo 做了什麼? Foo 做的就是 ...
  • 如果需要做一個類的重寫,需要重新寫這個類的所有屬性和函數,本文提供一個簡單的方法讓大家快速重寫一個類的所有屬性和函數 ...
  • 本文依舊是一篇譯文,寫於作者在開發.net core 半年後的進階學習時刻! 這篇文章很長,一口氣看完得花二十分鐘,大家要做好心理準備! 摘要:Java社群近來掀起了一陣輕量級容器的熱潮,這些容器能夠幫助開發者將來自不同項目的組件組裝成為一個內聚的應用程式。在它們的背後有著同一個模式,這個模式決定了 ...
  • 1.const是不變常量,在編譯的時候就需要有確定的值,只能用於數值和字元串,或者引用類型只能為null.(這裡為什麼要把字元串單獨拿出來?是因為字元串string是引用類型,但是使用的時候卻感覺是值類型,它是一種特殊的引用類型,後面會詳細說),struct也不能用const標記。const可以修飾 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...