概述

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 :
                        ;

    // width should be the displayable width
    containerWidth = containerWidth - Padding.Left - Padding.Right - panelMargin;
    )
    {
        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);
     && 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 (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!!!

New UWP Community Toolkit - AdaptiveGridView的更多相关文章

  1. New UWP Community Toolkit

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

  2. New UWP Community Toolkit - XAML Brushes

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

  3. New UWP Community Toolkit - Markdown

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

  4. New UWP Community Toolkit - Staggered panel

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

  5. New UWP Community Toolkit - Carousel

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

  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. Java注解学习

    一.注解定义 JVM5.0定义了4个标准的元注解: @Target, @Retention, @Documented @Inherited 1. @Target 作用:用于描述注解的使用范围 取值El ...

  2. linux 添加ftp用户与登录配置详解

    不同类Unix有一定区别 版本不同也有些区别 在linux主机上如何添加ftp用户 (一)修改配置文件 vi /etc/vsftpd/vsftpd.conf 在96行,97,98行 96 chroot ...

  3. Android OpenGL ES 开发(九): OpenGL ES 纹理贴图

    一.概念 一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture).当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实.当前流行的图形系统中,纹理绘制已经成为 ...

  4. NancyFX 第六章 视图引擎

    正如其他的Web工具包,Nancy也有视图的概念,用来描述在浏览器上看到的输出 视图的定义 你可能没有之前没有接触过"视图"的概念,或是仅仅是从其他工具包例如ASP.NET MVC ...

  5. 26.Django模板语言和分页

    继承 extends 子版只能继承一个父模板 1.父模板 master.html <!DOCTYPE html> <html lang="en"> < ...

  6. About Windows 10 SDK Preview Build 17110

    在 Windows Developer Day 活动同时,微软正式 Release 了 Windows 10 SDK Preview Build 17110. Windows 10 SDK Previ ...

  7. git pull error

    在图形界面中,执行拉取操作时,出现下面的错误. You asked to pull from the remote 'origin', but did not specifya branch. Bec ...

  8. memcached源码剖析——流程图

    参考: http://blog.csdn.net/column/details/memcached-src.html http://calixwu.com/2014/11/memcached-yuan ...

  9. NET Core2.0 Memcached踩坑,基于EnyimMemcachedCore整理MemcachedHelper帮助类。

    DotNetCore2.0下使用memcached缓存. Memcached目前微软暂未支持,暂只支持Redis,由于项目历史原因,先用博客园开源项目EnyimMemcachedCore,后续用到的时 ...

  10. Redis的持久化机制包括RBD和AOF两种,对于这两种持久化方式各有优势

    RDB机制的策略 RDB持久化是指在指定的时间间隔内将内存中的数据和操作通过快照的方式保存到redis bin目录下的一个默认名为 dump.rdb的文件,可以通过配置设置自动的快照持久化的方式,我们 ...