New UWP Community Toolkit - AdaptiveGridView
概述
UWP Community Toolkit 中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解 AdaptiveGridView 的实现。
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的更多相关文章
- New UWP Community Toolkit
概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...
- New UWP Community Toolkit - XAML Brushes
概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...
- New UWP Community Toolkit - Markdown
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...
- New UWP Community Toolkit - Staggered panel
概述 前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解 St ...
- New UWP Community Toolkit - Carousel
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解 Carousel 的实现. Carousel 是 ...
- New UWP Community Toolkit - RadialProgressBar
概述 UWP Community Toolkit 中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解 RadialProgressBar 的实现. Radi ...
- New UWP Community Toolkit - RadialGauge
概述 New UWP Community Toolkit V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解 RadialGauge 的实现. Radi ...
- New UWP Community Toolkit - RangeSelector
概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 Ra ...
- New UWP Community Toolkit - ImageEx
概述 UWP Community Toolkit 中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解 ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...
随机推荐
- [BZOJ1024] [SCOI2009] 生日快乐 (搜索)
Description windy的生日到了,为了庆祝生日,他的朋友们帮他买了一个边长分别为 X 和 Y 的矩形蛋糕.现在包括windy,一共有 N 个人来分这块大蛋糕,要求每个人必须获得相同面积的蛋 ...
- 分享一下我进入IT行业的经历
今天突然根想写博客,就注册了一个,分享一下我的成长经历. 我第一次接触编程的时候是在上大学的时候,我学的专业是工程测量接触的第一个语言是vb,我记得很清楚,我当时写出第一个小Demo是的心情,感觉到了 ...
- CSS黄金三段--消除边框的影响
以前常常因为padding.margin等影响了整个边框的大小,只能傻傻的算大小.现在的黄金三段真的是帮了大忙了,下面的代码放在CSS相应的内容中,就可以使得边框的大小就是你设置的width的属性值. ...
- linux优化项
Linux优化: 1.建立普通账号,使用普通用户登陆. 2.处理SELINUX. 3.处理防火墙. 4.精简开机自启动服务.保留sshd,network,crond,rsyslog,sysstat. ...
- wireshark抓包看ECN
由于实验需要,要统计ECN信息.为了验证拓扑中是否真的有ECN信息,用了wireshark进行抓包查看. 网上找到的相关有用资料有:http://blog.csdn.net/u011414200/ar ...
- 关系型数据库工作原理-SQL重写(翻译自Coding-Geek文章)
本文翻译自Coding-Geek文章:< How does a relational database work>.原文链接:http://coding-geek.com/how-data ...
- webpack使用教程
webpack使用教程 接触webpack也有挺长一段时间了,公司的项目也是一直用着webpack在打包处理,但前几天在教新人的情况下,遇到了一个问题,那就是:尽管网上的webpack教程满天飞,但是 ...
- 《Java2 实用教程(第五版)》学习指导
<Java2 实用教程(第五版)> 第1章Java入门 主要内容:P1 1.1Java的地位:P1 1.2Java的特点:P2 1.3安装JDK:P5 1.4Java程序的开发步骤:P8 ...
- 关于ASP.NET MVC的js和css资源管理
本文来源于博客园-钱智慧,转载请注明出处 通过这篇文章和这个回答,我们总结如下: 浏览器针对一个域名,最多只会开启6个线程来加载文件,比如head中如果有7个引入文件(js文件或者css文件)的标签, ...
- 剖析非同质化代币ERC721-全面解析ERC721标准
什么是ERC-721?现在我们看到的各种加密猫猫狗狗都是基于ERC-721创造出来的,每只都是一个独一无二的ERC-721代币,不过ERC-721在区块链世界远不止猫猫狗狗,它更大的想象空间在于将物理 ...