New UWP Community Toolkit - AdaptiveGridView

来源:https://www.cnblogs.com/shaomeng/archive/2018/04/04/8695814.html
-Advertisement-
Play Games

概述 UWP Community Toolkit 中有一個自適應的 GridView 控制項 - AdaptiveGridView,本篇我們結合代碼詳細講解 AdaptiveGridView 的實現。 AdaptiveGridView 控制項能夠以均勻分組的方式,讓一組列填充整個顯示空間,它可以對佈局和 ...


概述

UWP Community Toolkit  中有一個自適應的 GridView 控制項 - AdaptiveGridView,本篇我們結合代碼詳細講解  AdaptiveGridView 的實現。

AdaptiveGridView 控制項能夠以均勻分組的方式,讓一組列填充整個顯示空間,它可以對佈局和內容的變化做出反應,以便自動適應不同的外觀。我們來看一下官方示例的展示:

Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/AdaptiveGridView

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/adaptivegridview

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

 

開發過程

代碼分析

我們先來看看 AdaptiveGridView 控制項的類構成:

  • AdaptiveGridView.Properties.cs - AdaptiveGridView 控制項的依賴屬性類;
  • AdaptiveGridView.cs - AdaptiveGridView 控制項的定義和事件處理類;
  • AdaptiveHeightValueConverter.cs - 自適應高度轉換器,根據傳入的 value: ItemHeight,以及 padding、margin 等參數得到自適應高度;

1. AdaptiveGridView.Properties.cs

AdaptiveGridView 控制項的依賴屬性類,包括了以下屬性:

  • ItemClickCommand - 元素點擊命令
  • ItemHeight - 元素高度
  • ItemWidth - 元素寬度
  • OneRowModeEnabled - 單行模式可用性標誌,布爾值
  • DesiredWidth - 元素的期望寬度
  • StretchContentForSingleRow - 內容知否已經拉伸去填充一行,布爾值 

另外類中還有一個方法 CalculateColumns(containerWidth, itemWidth), 根據容器寬度和元素寬度,確定控制項應該包含幾列,向下取整,最小值為 1;

2. AdaptiveGridView.cs

AdaptiveGridView 類繼承自 GridView 類, 先來看一下類結構:

因為繼承自 GridView 類,所以 AdaptiveGridView 重載了兩個方法:

  • PrepareContainerForItemOverride(d, item) - 準備特定的 element 去顯示特定的 item;當 d 為 FrameworkElement 類型時,綁定 ItemWidth 和 ItemHeight 屬性;當為 ContentControl 類型時,HorizontalContentAlignment 和 VerticalContentAlignment 設為 Stretch;
  • OnApplyTemplate() - 針對單行的狀態變化,調用 DetermineOneRowMode() 方法做顯示的處理;

 接下來看一下幾個重要的事件處理方法:

① RecalculateLayout(ActualWidth)

RecalculateLayout(ActualWidth) 方法會在 item 數量變化,尺寸變化,控制項尺寸變化等觸發時調用,根據 panel 的 Margin 和 AdaptiveGridView 的 Padding 來調整 containerWidth,再調用 CalculateItemWidth(containerWidth) 方法計算得到 ItemWidth。

private void RecalculateLayout(double containerWidth)
{
    var itemsPanel = ItemsPanelRoot as Panel;
    var panelMargin = itemsPanel != null ?
                        itemsPanel.Margin.Left + itemsPanel.Margin.Right :
                        0;

    // width should be the displayable width
    containerWidth = containerWidth - Padding.Left - Padding.Right - panelMargin;
    if (containerWidth > 0)
    {
        var newWidth = CalculateItemWidth(containerWidth);
        ItemWidth = Math.Floor(newWidth);
    }
}

② CalculateItemWidth(containerWidth)

計算 item 的寬度;根據 containerWidth 和 item 的 DesiredWidth 計算出控制項的列數;如果需要針對單行模式調整,則調整列數為實際 item 數量;獲取 ItemMargin,當 items 或 container 為空時,設置為需要 container 的 Margin;最後根據 每一列在 container 中的寬度,減掉 itemMargin,得到 itemWidth;

protected virtual double CalculateItemWidth(double containerWidth)
{
    if (double.IsNaN(DesiredWidth))
    {
        return DesiredWidth;
    }

    var columns = CalculateColumns(containerWidth, DesiredWidth);

    // If there's less items than there's columns, reduce the column count (if requested);
    if (Items != null && Items.Count > 0 && Items.Count < columns && StretchContentForSingleRow)
    {
        columns = Items.Count;
    }

    // subtract the margin from the width so we place the correct width for placement
    var fallbackThickness = default(Thickness);
    var itemMargin = AdaptiveHeightValueConverter.GetItemMargin(this, fallbackThickness);
    if (itemMargin == fallbackThickness)
    {
        // No style explicitly defined, or no items or no container for the items
        // We need to get an actual margin for proper layout
        _needContainerMarginForLayout = true;
    }

    return (containerWidth / columns) - itemMargin.Left - itemMargin.Right;
}

③ DetermineOneRowMode()

單行模式和多行模式切換時的處理;當單行時,把 MaxHeight 屬性設置為 ItemHeight,Orientation 設為縱向,滾動設置包括縱向滾動禁止,隱藏滾動條,橫向滾動可用;如果為多行模式,則根據保存的 Orientation 和 滾動條屬性恢復顯示;

private void DetermineOneRowMode()
{
    if (_isLoaded)
    {
        var itemsWrapGridPanel = ItemsPanelRoot as ItemsWrapGrid;

        if (OneRowModeEnabled)
        {
            var b = new Binding()
            {
                Source = this,
                Path = new PropertyPath("ItemHeight"),
                Converter = new AdaptiveHeightValueConverter(),
                ConverterParameter = this
            };

            if (itemsWrapGridPanel != null)
            {
                _savedOrientation = itemsWrapGridPanel.Orientation;
                itemsWrapGridPanel.Orientation = Orientation.Vertical;
            }

            SetBinding(MaxHeightProperty, b);

            _savedHorizontalScrollMode = ScrollViewer.GetHorizontalScrollMode(this);
            _savedVerticalScrollMode = ScrollViewer.GetVerticalScrollMode(this);
            _savedHorizontalScrollBarVisibility = ScrollViewer.GetHorizontalScrollBarVisibility(this);
            _savedVerticalScrollBarVisibility = ScrollViewer.GetVerticalScrollBarVisibility(this);
            _needToRestoreScrollStates = true;

            ScrollViewer.SetVerticalScrollMode(this, ScrollMode.Disabled);
            ScrollViewer.SetVerticalScrollBarVisibility(this, ScrollBarVisibility.Hidden);
            ScrollViewer.SetHorizontalScrollBarVisibility(this, ScrollBarVisibility.Visible);
            ScrollViewer.SetHorizontalScrollMode(this, ScrollMode.Enabled);
        }
        else
        {
            ClearValue(MaxHeightProperty);

            if (!_needToRestoreScrollStates)
            {
                return;
            }

            _needToRestoreScrollStates = false;

            if (itemsWrapGridPanel != null)
            {
                itemsWrapGridPanel.Orientation = _savedOrientation;
            }

            ScrollViewer.SetVerticalScrollMode(this, _savedVerticalScrollMode);
            ScrollViewer.SetVerticalScrollBarVisibility(this, _savedVerticalScrollBarVisibility);
            ScrollViewer.SetHorizontalScrollBarVisibility(this, _savedHorizontalScrollBarVisibility);
            ScrollViewer.SetHorizontalScrollMode(this, _savedHorizontalScrollMode);
        }
    }
}

④ OnSizeChanged(sender, e)

在尺寸變化時,如果橫向不是拉伸狀態,則需要計算變化前後的列數是否有變化,如果有變化則重新計算佈局;如果是拉伸狀態,則尺寸變化時直接重新計算佈局;

private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
    // If we are in center alignment, we only care about relayout if the number of columns we can display changes
    // Fixes #1737
    if (HorizontalAlignment != HorizontalAlignment.Stretch)
    {
        var prevColumns = CalculateColumns(e.PreviousSize.Width, DesiredWidth);
        var newColumns = CalculateColumns(e.NewSize.Width, DesiredWidth);

        // If the width of the internal list view changes, check if more or less columns needs to be rendered.
        if (prevColumns != newColumns)
        {
            RecalculateLayout(e.NewSize.Width);
        }
    }
    else if (e.PreviousSize.Width != e.NewSize.Width)
    {
        // We need to recalculate width as our size changes to adjust internal items.
        RecalculateLayout(e.NewSize.Width);
    }
}

 

3. AdaptiveHeightValueConverter.cs

自適應高度轉換器,單向轉換,根據傳入的 value: ItemHeight,以及 padding、margin 等參數得到自適應高度;轉換隻在 OneRowMode 時使用,作用是把原高度,加上 padding 和 margin 變成新的高度,效果就是單行模式時,元素在高度上沒有空隙;設置的 Item padding 和 margin 會失效;

public object Convert(object value, Type targetType, object parameter, string language)
{
    if (value != null)
    {
        var gridView = (GridView)parameter;
        if (gridView == null)
        {
            return value;
        }

        double.TryParse(value.ToString(), out double height);

        var padding = gridView.Padding;
        var margin = GetItemMargin(gridView, DefaultItemMargin);
        height = height + margin.Top + margin.Bottom + padding.Top + padding.Bottom;

        return height;
    }

    return double.NaN;
}

AdaptiveHeightValueConverter 類中還有一個方法 GetItemMargin(view, fallback), 在 AdaptiveGridView 類的 CalculateItemWidth(containerWidth) 方法中使用,值設置的優先順序是:先取 GridView 對應的 Margin 屬性值,如果為空,則取 GridViewItem 的 Margin 屬性值,如果也為空,則取預設值;

internal static Thickness GetItemMargin(GridView view, Thickness fallback = default(Thickness))
{
    var setter = view.ItemContainerStyle?.Setters.OfType<Setter>().FirstOrDefault(s => s.Property == FrameworkElement.MarginProperty);
    if (setter != null)
    {
        return (Thickness)setter.Value;
    }
    else
    {
        if (view.Items.Count > 0)
        {
            var container = (GridViewItem)view.ContainerFromIndex(0);
            if (container != null)
            {
                return container.Margin;
            }
        }

        // Use the default thickness for a GridViewItem
        return fallback;
    }
}

 

調用示例

我們簡單調用 AdaptiveGridView 控制項,設置了 DesiredWidth 和 ItemHeight,選擇模式設置為多選;可以看到在控制項尺寸變化時,列數和 Item 尺寸都發生了變化;如果不設置 ItemHeight,則每一行都會占滿寬度;第三張圖,當設置單行模式時,Item 在一行排列;

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <controls:AdaptiveGridView Name="AdaptiveGridViewControl"
                                OneRowModeEnabled="False"
                                ItemHeight="145"
                                DesiredWidth="157"
                                SelectionMode="Multiple"
                                IsItemClickEnabled="True"
                                ItemTemplate="{StaticResource PhotosTemplate}"/>
</Grid>

 

 

總結

到這裡我們就把 UWP Community Toolkit 中的 AdaptiveGridView 控制項的源代碼實現過程和簡單的調用示例講解完成了,希望能對大家更好的理解和使用這個控制項有所幫助。歡迎大家多多交流,謝謝!

最後,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490大家可以通過微博關註最新動態。

衷心感謝 UWPCommunityToolkit 的作者們傑出的工作,Thank you so much, UWPCommunityToolkit authors!!!

 


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

-Advertisement-
Play Games
更多相關文章
  • 閱讀目錄 第一篇:python入門 第二篇:數據類型、字元編碼、文件處理 第三篇:函數 第四篇:模塊與包 第五篇:常用模塊 第六篇:面向對象 第七篇:面向對象高級 第八篇:異常處理 第九篇:網路編程 第十篇:併發編程 第十一篇:Mysql系列 更新中... 第一篇:python入門 第二篇:數據類型 ...
  • 把之前發表在微信公眾號的爬蟲系列文章遷移過來,熱熱身,就當備份了。 手把手教你寫網路爬蟲(1) 作者:拓海 摘要:從零開始寫爬蟲,初學者的速成指南! 封面: 大家好,《手把手教你寫網路爬蟲》連載開始了!在筆者的職業生涯中,幾乎沒有發現像網路爬蟲這樣的編程實踐,可以同時吸引程式員和門外漢的註意。本文由 ...
  • 會話跟蹤是Web程式中常用的技術,用來跟蹤用戶的整個會話。常用的會話跟蹤技術是Cookie與Session。Cookie通過在客戶端記錄信息確定用戶身份,Session通過在伺服器端記錄信息確定用戶身份。 本文將講解Cookie和Session以及它們的區別。 ...
  • php捷豹路虎 品牌全車零件訂購平臺( php源碼 )php+mysql 架構的平臺型 車零件訂購系統平臺軟體, 已做了數據採集。目前擁有捷豹路虎 幾十G的剖視圖、圖冊、系統(如發動機系統)、零件完整數據。 路虎、捷豹零件數據完整,包括這2款車的 系統圖冊、VIN車輛唯一識別碼查詢及其配件、每個系統 ...
  • 版本:win10系統 virtualbox:5.1.26 vagrant :1.9.7 centos 7.0 xshell/git 首先下載好對應版本的軟體 配置vagrant和virtualbox 一.把虛擬機載入到box容器中 二.找一個/新建一個目錄,例如vagrant,然後初始化環境 初始化 ...
  • Java性能問題一直困擾著廣大程式員,由於平臺複雜性,要定位問題,找出其根源確實很難。隨著10多年Java平臺的改進以及新出現的多核多處理器,Java軟體的性能和擴展性已經今非昔比了。現代JVM持續演進,內建了更為成熟的優化技術、運行時技術和垃圾收集器。與此同時,底層的硬體平臺和操作系統也在演化。 ...
  • 簡介 SourceTree 是一款擁有可視化界面的項目版本控制軟體,適用於git項目管理,同時它集成了 git flow 工作流程,對於不熟悉 git 命令的初學者來說,可以通過 SourceTree 快速學會使用 Git 和 git flow 來參與代碼版本管理和團隊協作開發。 問題 今日在全新的 ...
  • 最近由於工作需要,開始寫托管C++,由於C++11中的mutex,和future等類,托管C++不讓調用(報錯),所以自己實現了托管C++的線程鎖。 該類可確保當一個線程位於代碼的臨界區時,另一個線程不會進入該臨界區。 如果其他線程嘗試進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...