飞流直下的精彩 -- 淘宝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(2));

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 < 1)

{

throw (new ArgumentOutOfRangeException("NumberOfColumnsOrRows", "NumberOfColumnsOrRows must >0"));//太窄

}

var LenList = new List<double>();

for (int i = 0; i < NumberOfColumnsOrRows; i++)

{

LenList.Add(0);

}

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[0];

int minP = 0;

for (int i = 1; i < NumberOfColumnsOrRows; i++)

{

if (LenList[i] < minLen)

{

minLen = LenList[i];

minP = i;

}

}

LenList[minP] += itemHeight;

}

var maxLen = LenList[0];

int maxP = 0;

for (int i = 1; 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[0];

int minP = 0;

for (int i = 1; i < NumberOfColumnsOrRows; i++)

{

if (LenList[i] < minLen)

{

minLen = LenList[i];

minP = i;

}

}

LenList[minP] += itemWidth;

}

var maxLen = LenList[0];

int maxP = 0;

for (int i = 1; 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 < 1)

{

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 = 0; i < NumberOfColumnsOrRows; i++)

{

LenList.Add(0);

posXorYList.Add(i * maxWidth);

}

foreach (var item in Children)

{

var itemHeight = item.DesiredSize.Height;

var minLen = LenList[0];

int minP = 0;

for (int i = 1; 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[0];

int maxP = 0;

for (int i = 1; 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 = 0; i < NumberOfColumnsOrRows; i++)

{

LenList.Add(0);

posXorYList.Add(i * maxHeight);

}

foreach (var item in Children)

{

var itemWidth = item.DesiredSize.Width;

var minLen = LenList[0];

int minP = 0;

for (int i = 1; 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[0];

int maxP = 0;

for (int i = 1; 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应用更加完善。

时间: 2024-07-31 08:51:58

飞流直下的精彩 -- 淘宝UWP中瀑布流列表的实现的相关文章

PC版淘宝UWP揭秘

亲们,还记得一个月前我们的承诺吧?11.11,我们按时上线了手机淘宝UWP的Mobile版本(以下简称M版),感谢所有参与Windows Phone Insider Program的手机用户们,他们中有80%的热心用户下载了手机淘宝的Mobile版本,并给我们提供了大量有用的反馈,鼓励和鞭策我们不断改进产品质量. 我们的另一个承诺也即将兑现,那就是目标为12.12上线的淘宝UWP的Desktop版(以下简称D版)本.目前开发工作已经接近尾声,即将进行alpha内测,很快将会进行beta公测.希望

剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记

前言 网购已经不再是现在的时髦,而变成了我们每天的日常生活.上网已经和买买买紧密地联系在了一起,成为了我们的人生信条.而逛街一词,越来越多地变成了一种情怀.有时候我们去逛街,要么是为了打发时间,要么是想亲手摸摸商品本身,要么就是想看看不同的商品,放在眼前或者在脑海里比较一下.毕竟现在网上琳琅满目的商品让人眼花缭乱,一次展示一个,看完这个,忘了上一个:看完了最后一个,已经没有力气再打开长长的历史列表一个一个看回去.如果没有石猴的火眼金睛,如何万里挑一,找到自己中意的那个‘它’呢?毕竟我们大多数人,

从淘宝 UWP 的新功能 -- 比较页面来谈谈 UWP 的窗口多开功能

前言 之前在 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记 这篇随笔中介绍了一下 UWP 淘宝的“比较”新功能呱呱坠地的过程.在鲜活的文字背后,其实都是程序员不眠不休的血泪史(有血有泪有史)……所以我们这次就要在看似好玩的 UWP 多窗口实现背后,挖掘一些我们也是首次接触的干活“新鲜热辣”地放松给大家.希望能使大家在想要将自己的 APP 开新窗口的时候,能从本文中得到一些启发,而不是总是发现 C# 关于 UWP 开新窗口可供参考的文章只有 Is it possible to open

淘宝UWP桌面版已经发布

目前正在等待应用商店的检测,很快会可以下载. 谢谢各位园主针对淘宝UWP 桌面版(又叫PC版,HD版等等)给予的feedback,在这里统一回复一下,就不一一感谢了. 有一件事需要说明一下,请看下图: 绿色方框的部分是H5页面,这里的内容完全有淘宝服务器端控制,所以由此产生的bug我们这边修不了,但是淘宝的攻城狮们已经知道了这些bug,正在努力修改. 如何区别哪些是H5页面呢?1)在右侧窄屏显示:2)标题条上的标题在中间:3)标题条右端有刷新和菜单(…)两个按钮.

淘宝运营中需注意哪些问题_简图电商

淘宝运营中需注意的一些细节 很多人在运营的过程中有一些小小的错误自己却没有发觉到,却在这个陷阱中很难爬起来.下面简图电商给大家介绍的是一些大家在运营过程中常常容易想错的细节,可定能帮助到大家! 1.拍下到支付转化率,是不是越高越好? 现在很多客单价高的行业,都会建立自己店铺的每天咨询未付款客户id的表格,然后会有后续员工跟进催付,以此来提高销售额.所以,贾真做顾问的家具店支付转化率高达以上80%以上,并且引以为傲. 然后,昨天听某知名家具电商的运营分享,因为家具客单价高,做决定往往要夫妻共同拍板

手机淘宝推荐中的排序学习

原文:http://yq.aliyun.com/articles/122?spm=0.0.0.0.oL8bTY 周梁:淘宝推荐机器学习技术专家,中国科学院自动化研究所机器学习博士,主要研究工作方向是机器学习.大规模并行算法优化.先后从事过广告CTR预估,MPI机器学习平台搭建,手淘个性化推荐等多方面工作. 排序学习是推荐.搜索.广告的核心问题.在手机淘宝的推荐场景中,受制于展示空间的限制,排序学习显得尤为重要.在淘宝,如何从十亿的商品中,挑选出用户 今天喜欢的商品,也是个巨大的挑战. 本次我们分

淘宝运营中的6大致命误区,你犯过么?

今天开门见山!所谓的致命误区就是很有可能是运营中的小细节没有处理好,就导致你全盘皆输,然后跌倒了就没有了爬起来的机会.下面这六个运营过程中可能会犯的致命错误都是一些血泪的教训,绝对值得引以为鉴! 1. 急病乱投医 照搬别人的经验技巧 赚 钱是学习极其重要的一个源动力,尤其对于淘宝店主来讲,很多上学时极其不爱学习的人,为了店铺生意,上派代.报培训班.参加线下培训,不但舍得花钱,还舍 得花时间和精力.好了,我们暂且不探讨执行力的因素,假设大家学习了都能够去很好的执行,但是你们有没有悲催的发现:很多大

分享给微信好友的淘宝乱码中隐藏了什么秘密

马上要过生日了,我尝试在淘宝找到了自己喜欢的同款礼物,点击右上角分享到她的微信,看她能不能读懂我的小心思.这个分享的过程涉及到一段"乱码"(淘口令,后续均以淘口令来称呼),可别小看这段淘口令,它包含了很多信息.今天主要就是分享一下为什么淘宝分享出来的内容是淘口令?淘口令包含了什么信息?以及解析这段工具是怎么制作的. 这是一个系列文章,涉及淘客工具的使用与制作,后续陆续会完成一个整套系列工具的开发,欢迎收藏关注看整个系列.文中可能涉及到链接信息,仅仅只是做程序解释演示使用,并不能直接达到

详解如何利用淘宝外部低价促销引流平台提高销量

“日前阿里巴巴零售平台开出5月第二张罚单,47个集市淘宝商家被查实参与“仿真刷单”,其中有26个皇冠级卖家,最高星级为4皇冠,开店最长的已超过八年.”--网易新闻 2015年的电商之路,注定是最艰难的一年!之前流行的一句话,“刷单是找死,不刷单是等死”,果真应验了. 显然,刷单致富路已到了尽头,对于实力不强的中小卖家来说,这无疑是最致命的打击了! 然而,除了刷单之外,就没有别的办法提高销量了吗?好在天无绝人之路,小编有幸采访了一位淘宝运营高手,从他那里了解到一些通过淘宝外部平台进行低价促销提高销