飛流直下的精彩 -- 淘寶UWP中瀑布流列表的實現

在淘寶UWP中,搜索結果列表是用戶瞭解寶貝的重要一環,其中的圖片效果對吸引用戶點擊搜索結果,查看寶貝詳情有比較大的影響。爲此手機淘寶特地在搜索結果列表上採用了2種表現方式:一種就是普通的列表模式,而另外一種則是突出寶貝圖片的瀑布流模式。html

若是用戶搜索某些關鍵字,如女裝類的狀況下,淘寶的搜索結果會自動切換到瀑布流模式,讓寶貝的美圖更加衝擊用戶的視覺。ide

可是UWP默認的列表控件並無這種效果,listview控件中雖然子元素能夠不同大小,可是隻能有1列,gridview控件雖然有多列,但每一個子元素都只能取相同大小。通過一番搜索,也只有元素由固定大小的不一樣倍數構成的gridview控件可使用,但效果並不理想。那麼咱們有沒有辦法能獲得瀑布流的效果的控件呢?答案是確定的。咱們可能記得在listview中,若是咱們要改變列表的擴展方向,須要在xaml中定義listview的itemspanel:佈局

 <ListView>

<ListView.ItemsPanel>

<ItemsPanelTemplate>

<ItemsWrapGrid Orientation="Horizontal"></ItemsWrapGrid>

</ItemsPanelTemplate>

</ListView.ItemsPanel>

</ListView> 
View Code

 

在gridview中設置最大的行數或列數時,咱們也要定義ItemsWrapGrid。優化

這裏的ItemsStackPanel,ItemsWrapGrid與咱們以前在淘寶UWP--自定義Panel中所提到的panel有什麼關係呢?spa

實際上它們都是繼承自panel的FrameworkElement,也就是說它們均可以對內部的子元素進行佈局。無論listview仍是gridview,他們列表的形式都是由itemsPanel決定的,listview只有1列,能夠縱向或者橫向擴展,是由它使用的itemsPanel- ItemsStackPanel肯定的,gridview能夠有多列,能夠縱向或者橫向擴展,也是由它使用了ItemsWrapGrid做爲itemsPanel來決定的。那麼若是咱們根據淘寶UWP--自定義Panel中提到的方法,自定義一個panel,就能夠實現瀑布流中形式的列表了。code

整理需求

肯定了要實現一個瀑布流的佈局panel,咱們接下來考慮一下咱們的具體有哪些需求呢?在淘寶的搜索結果瀑布流中,只用了2列。可是考慮到咱們的淘寶UWP可能運行在PC或者平板等橫向屏幕的設備上,若是也用2列的話會有不少圖只能在屏幕中顯示一部分。因此在PC或者平板等橫向屏幕的設備上,咱們要讓瀑布流的列數增長,也就是說咱們的panel須要能自定義列數。htm

在淘寶的搜索結果瀑布流中,寶貝的搜索結果是縱向擴展的,那麼有沒有可能有狀況須要使用橫向擴展的瀑布流呢?想一想彷佛是比較酷的,那麼就爲咱們的panel加上擴展方向的選擇吧。blog

着手實現

在肯定了具體需求以後就能夠開始着手實現咱們的自定義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);

}

}
View Code

 

這就是咱們的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);

}

} 
View Code

 

接下來實現咱們的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);

}

 

}
View Code

 

在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應用更加完善。

相關文章
相關標籤/搜索