飞流直下的精彩 -- 淘宝UWP中瀑布流列表的实现
在淘宝UWP中,搜索结果列表是用户了解宝贝的重要一环,其中的图片效果对吸引用户点击搜索结果,查看宝贝详情有比较大的影响。为此手机淘宝特意在搜索结果列表上采用了2种表现方式:一种就是普通的列表模式,而另一种则是突出宝贝图片的瀑布流模式。
如果用户搜索某些关键字,如女装类的情况下,淘宝的搜索结果会自动切换到瀑布流模式,让宝贝的美图更加冲击用户的视觉。
但是UWP默认的列表控件并没有这种效果,listview控件中虽然子元素可以不一样大小,但是只能有1列,gridview控件虽然有多列,但每个子元素都只能取相同大小。经过一番搜索,也只有元素由固定大小的不同倍数构成的gridview控件可以使用,但效果并不理想。那么我们有没有办法能得到瀑布流的效果的控件呢?答案是肯定的。我们可能记得在listview中,如果我们要改变列表的扩展方向,需要在xaml中定义listview的itemspanel:
<ListView> <ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsWrapGrid Orientation="Horizontal"></ItemsWrapGrid> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView>
在gridview中设置最大的行数或列数时,我们也要定义ItemsWrapGrid。
这里的ItemsStackPanel,ItemsWrapGrid与我们之前在淘宝UWP--自定义Panel中所提到的panel有什么关系呢?
实际上它们都是继承自panel的FrameworkElement,也就是说它们都可以对内部的子元素进行布局。不管listview还是gridview,他们列表的形式都是由itemsPanel决定的,listview只有1列,可以纵向或者横向扩展,是由它使用的itemsPanel- ItemsStackPanel确定的,gridview可以有多列,可以纵向或者横向扩展,也是由它使用了ItemsWrapGrid作为itemsPanel来决定的。那么如果我们根据淘宝UWP--自定义Panel中提到的方法,自定义一个panel,就可以实现瀑布流中形式的列表了。
整理需求
确定了要实现一个瀑布流的布局panel,我们接下来考虑一下我们的具体有哪些需求呢?在淘宝的搜索结果瀑布流中,只用了2列。但是考虑到我们的淘宝UWP可能运行在PC或者平板等横向屏幕的设备上,如果也用2列的话会有很多图只能在屏幕中显示一部分。所以在PC或者平板等横向屏幕的设备上,我们要让瀑布流的列数增加,也就是说我们的panel需要能自定义列数。
在淘宝的搜索结果瀑布流中,宝贝的搜索结果是纵向扩展的,那么有没有可能有情况需要使用横向扩展的瀑布流呢?想想似乎是比较酷的,那么就为我们的panel加上扩展方向的选择吧。
着手实现
在确定了具体需求之后就可以开始着手实现我们的自定义panel了。
我们的面板的名字就叫WaterfallPanel吧,需要继承panel类型,能定义行数或者列数NumberOfColumnsOrRows,能定义扩展方向WaterfallOrientation,并实现MeasureOverride和ArrangeOverride方法:
public class WaterfallPanel :Panel { public int NumbersOfColumnsOrRows { get { return (int)GetValue(NumbersOfColumnsOrRowsProperty); } set { SetValue(NumbersOfColumnsOrRowsProperty, value); } } // Using a DependencyProperty as the backing store for NumbersOfColumnsOrRows. This enables animation, styling, binding, etc... public static readonly DependencyProperty NumbersOfColumnsOrRowsProperty = DependencyProperty.Register("NumbersOfColumnsOrRows", typeof(int), typeof(WaterfallPanel), new PropertyMetadata()); public Orientation WaterfallOrientation { get { return (Orientation)GetValue(WaterfallOrientationProperty); } set { SetValue(WaterfallOrientationProperty, value); } } // Using a DependencyProperty as the backing store for WaterfallOrientation. This enables animation, styling, binding, etc... public static readonly DependencyProperty WaterfallOrientationProperty = DependencyProperty.Register("WaterfallOrientation", typeof(Orientation), typeof(WaterfallPanel), new PropertyMetadata(Orientation.Vertical)); protected override Size MeasureOverride(Size availableSize) { return base.MeasureOverride(availableSize); } protected override Size ArrangeOverride(Size finalSize) { return base.ArrangeOverride(finalSize); } }
这就是我们的panel的雏形了,需要注意的是我们的NumberOfColumnsOrRows,和WaterfallOrientation属性需要能在xaml中调用,因此必须写成DependencyProperty的形式。在写的时候可以用先输入propdp,再按tab键,在vs自动生成的模板上进行修改的方法,能方便很多。考虑到用户也可能会不输入行列数或者扩展方向,我们给了它们默认值显示2行或列,纵向扩展。
首先我们来实现MeasureOverride方法。MeasureOverride方法接受一个panel可以占据的空间大小availableSize,再根据这个availableSize给内部的子元素分配可以占据的空间大小。在瀑布流中,以纵向扩展为例,每个元素的最大宽度都是相等的,都是panel宽度的列数分之一。而每个元素的高度则可以自由扩展。因此根据这样的思路我们的MeasureOverride方法的实现应该是:
protected override Size MeasureOverride(Size availableSize) { if (NumberOfColumnsOrRows < ) { throw (new ArgumentOutOfRangeException("NumberOfColumnsOrRows", "NumberOfColumnsOrRows must >0"));//太窄 } var LenList = new List<double>(); for (int i = ; i < NumberOfColumnsOrRows; i++) { LenList.Add(); } if (WaterfallOrientation == Orientation.Vertical) { double maxWidth = availableSize.Width / NumberOfColumnsOrRows; Size maxSize = new Size(maxWidth, double.PositiveInfinity); foreach (var item in Children) { item.Measure(maxSize); var itemHeight = item.DesiredSize.Height; var minLen = LenList[]; int minP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] < minLen) { minLen = LenList[i]; minP = i; } } LenList[minP] += itemHeight; } var maxLen = LenList[]; int maxP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] > maxLen) { maxLen = LenList[i]; maxP = i; } } return new Size(availableSize.Width, LenList[maxP]); } else { double maxHeight = availableSize.Height / NumberOfColumnsOrRows; Size maxSize = new Size(double.PositiveInfinity, maxHeight); foreach (var item in Children) { item.Measure(maxSize); var itemWidth = item.DesiredSize.Width; var minLen = LenList[]; int minP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] < minLen) { minLen = LenList[i]; minP = i; } } LenList[minP] += itemWidth; } var maxLen = LenList[]; int maxP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] > maxLen) { maxLen = LenList[i]; maxP = i; } } return new Size(LenList[maxP], availableSize.Height); } }
接下来实现我们的ArrangeOverride方法。在ArrangeOverride方法中,会接受一个可以进行布局的空间大小finalSize,在这个空间中将子元素逐个定位在合适的位置。在我们的瀑布流panel中,我们要将子元素定位成瀑布流的效果。那么如何实现瀑布流的效果呢?以纵向的情况为例,瀑布流中每个元素的宽度一致而长度不一,排成一定数量的列,每列长度虽然参差但差距不大,并列排在panel中形成瀑布的样子。我们可以将panel分成若干列,将子元素分配到这些列中按纵向扩展的顺序排布,每次分配时都挑总长最短的列,将新元素分配到这列。这样就能让各个列的长度差距不大,满足瀑布流的效果。按照这个思路,我们实现了ArrangeOverride方法:
protected override Size ArrangeOverride(Size finalSize) { if (NumberOfColumnsOrRows < ) { throw (new ArgumentOutOfRangeException("NumberOfColumnsOrRows", "NumberOfColumnsOrRows must >0"));//太窄 } var LenList = new List<double>(); var posXorYList = new List<double>(); if (WaterfallOrientation == Orientation.Vertical) { double maxWidth = finalSize.Width / NumberOfColumnsOrRows; //列的长度和左上角的x值 for (int i = ; i < NumberOfColumnsOrRows; i++) { LenList.Add(); posXorYList.Add(i * maxWidth); } foreach (var item in Children) { var itemHeight = item.DesiredSize.Height; var minLen = LenList[]; int minP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] < minLen) { minLen = LenList[i]; minP = i; } } item.Arrange(new Rect(posXorYList[minP], LenList[minP], item.DesiredSize.Width, item.DesiredSize.Height)); LenList[minP] += item.DesiredSize.Height; } var maxLen = LenList[]; int maxP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] > maxLen) { maxLen = LenList[i]; maxP = i; } } return new Size(finalSize.Width, LenList[maxP]); } else { double maxHeight = finalSize.Height / NumberOfColumnsOrRows; //行的长度和左上角的y值 for (int i = ; i < NumberOfColumnsOrRows; i++) { LenList.Add(); posXorYList.Add(i * maxHeight); } foreach (var item in Children) { var itemWidth = item.DesiredSize.Width; var minLen = LenList[]; int minP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] < minLen) { minLen = LenList[i]; minP = i; } } item.Arrange(new Rect(LenList[minP], posXorYList[minP], item.DesiredSize.Width, item.DesiredSize.Height)); LenList[minP] += item.DesiredSize.Width; } var maxLen = LenList[]; int maxP = ; for (int i = ; i < NumberOfColumnsOrRows; i++) { if (LenList[i] > maxLen) { maxLen = LenList[i]; maxP = i; } } return new Size(LenList[maxP], finalSize.Height); } }
在MeasureOverride方法和ArrangeOverride方法实现之后,我们的瀑布流panel就可以说初步完成了。实际的运行效果和我们的淘宝UWP版中是基本一致的,只不过在淘宝UWP版的不断迭代中,我们又对一些细节做了优化。另外需要注意的是如果使用横向瀑布流,需要把WaterfallPanel所属的listview或gridview的scrollviewer相关的值进行设置:
ScrollViewer.VerticalScrollMode="Disabled" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled"
否则会由于listview或gridview的默认设置是纵向扩展,从而在MeasureOverride方法传入的availableSize的height是无限大,最终导致计算错误而应用崩溃。
这样看来只要掌握了方法和思路,自定义panel也并没有想象中那么困难。小伙伴们也可以尝试创建自己独有的列表控件,如果你有一些奇思妙想的话,也欢迎分享出来。
让我们共同进步,让UWP应用更加完善。
飞流直下的精彩 -- 淘宝UWP中瀑布流列表的实现的更多相关文章
- 淘宝UWP中的100个为什么
从淘宝UWP第一版发布到现在,已经有十个月了,期间收到了用户各种各样的反馈,感谢这些用户的反馈,指导我们不断的修正.完善应用.但是也有一部分需求或建议,由于资源或技术的限制,目前确实无法做到,只能对广 ...
- 我的面板我做主 -- 淘宝UWP中自定义Panel的实现
在Windows10 UWP开发平台上内置的XMAL布局面板包括RelativePanel.StackPanel.Grid.VariableSizedWrapGrid 和 Canvas.在开发淘宝UW ...
- 上传控件---淘宝kissy uploader+瀑布流显示
介绍Uploader : Uploader 是由阿里集团前端工程师们发起创建的一个开源 JS 框架.它具备模块化.高扩展性.组件齐全,接口一致.自主开发.适合多种应用场景等特性. Uploader是非 ...
- PC版淘宝UWP揭秘
经过第一轮内测后的bug数量:65 2015/11/27 - bug数量 = 60 2015/11/30 - bug数量 = 53 2015/12/1 - bug数量 = 49 2015/12/2 - ...
- 从淘宝 UWP 的新功能 -- 比较页面来谈谈 UWP 的窗口多开功能
前言 之前在 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记 这篇随笔中介绍了一下 UWP 淘宝的“比较”新功能呱呱坠地的过程.在鲜活的文字背后,其实都是程序员不眠不休的血泪史(有血有泪有史) ...
- 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记
前言 网购已经不再是现在的时髦,而变成了我们每天的日常生活.上网已经和买买买紧密地联系在了一起,成为了我们的人生信条.而逛街一词,越来越多地变成了一种情怀.有时候我们去逛街,要么是为了打发时间,要么是 ...
- 手机淘宝UWP
各位园主好! bug 走势: 哪天bug 足够少,哪天就可以发布了 :) 2015/10/23: 49 2015/10/26: 40 2015/10/27: 36 2015/10/28: 30 20 ...
- 淘宝UWP桌面版已经发布
目前正在等待应用商店的检测,很快会可以下载. 谢谢各位园主针对淘宝UWP 桌面版(又叫PC版,HD版等等)给予的feedback,在这里统一回复一下,就不一一感谢了. 有一件事需要说明一下,请看下图: ...
- php提取淘宝URL中ID的代码
一段可以提取淘宝URL中ID的PHP代码. 例如: <?php $taobao = 'taobao.com'; $tmall = 'tmall.com'; $guojitmall = 'tmal ...
随机推荐
- Linux学习内容
Linux学习要点(转载自红联) 一.学习Linux的基本要求1. 掌握至少50个以上的常用命令. 2. 熟悉Gnome/KDE等X-windows桌面环境操作 . 3. 掌握.tgz..rpm等软件 ...
- 自连接<EntityFramework6.0>
自引用 public class PictureCategory { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ...
- ExtJS客户端代理
代理(proxy)分为两大类:客户端代理和服务器端代理.客户端代理主要完成与浏览器本地存取数据相关的工作,服务器端代理则是通过发送请求,从服务器端获取数据.根据各自获取数据的方式,客户端代理和服务器端 ...
- Nginx - Windows下作为服务启动
Nginx官方没有提供作为服务启动nginx的方案.以服务启动nginx依赖于winsw,当前最新版是1.19. 参考:https://segmentfault.com/a/1190000006807 ...
- TotoiseSVN的基本使用方法
TotoiseSVN的基本使用方法 在 项目管理实践教程一.工欲善其事,必先利其器[Basic Tools]中,我已经讲解了怎样安装TortoiseSVN.在上面的讲解中已经讲了怎么使用VisualS ...
- System中记录体函数命名怪异
//1019unit System; 中发现记录体函数命名怪异//乍一看,很怪异,其实是结构体里面 的变量后面直接写 函数类型了.不像传统先定义T***Event = procedure(S ...
- 餐厅点餐系统app总结
总结: 三个冲刺已经结束,虽然没有说十分完美,但该实现的功能还是实现了,只是在市场是相较于专业性的缺乏竞争力,从界面到体验都需进一步优化. 每个人的进度不一样,为了同一个任务需要不断的磨合与合作,但慢 ...
- 0_MVC+EF+Autofac(dbfirst)轻型项目框架_基本框架
前言 原来一直使用他人的开源项目框架,异常的定位会很麻烦,甚至不知道这个异常来自我的代码还是这个框架本身.他人的框架有一定的制约性,也有可能是我对那些框架并没深入了解,因为这些开源框架在网上也很难找到 ...
- StatePattern
class Program { static void Main(string[] args) { var state = new OpeningState(); var lift = new Lif ...
- ITree诞生啦!
经过一个月的码码码,一个面向OIer的ITree终于来辣! ... (似乎把OI遗弃在了某个角落了........... 一个月里,从只会py到写出ITree,真是不容易呢(其实就是两个多礼拜而已= ...