最近有童鞋有這種需求,說實話我不知道這個Panel怎麼起名字。 效果連接https://tuchong.com/tags/風光/ 下麵是我做成的效果,可以規定每個Row的Items個數 2個 3個 4個 代碼在:GitHub 下麵我來說一下我的思路 其實很早之前就寫過這種可變大小的控制項,但這次的跟這 ...
最近有童鞋有這種需求,說實話我不知道這個Panel怎麼起名字。
效果連接https://tuchong.com/tags/風光/
下麵是我做成的效果,可以規定每個Row的Items個數
2個
3個
4個
代碼在:GitHub
下麵我來說一下我的思路
其實很早之前就寫過這種可變大小的控制項,但這次的跟這個需求有點變化,這個每一行個數一定,大小根據圖片的大小進行填充。
微軟預設的VariableSizedWrapGrid和Toolikt裡面的StaggeredPanel都會導致ListView失去一些特性(虛擬化,增量載入)
之前做另一種。其實現在這個差不多。就是ListViewItem 就是每一行,那麼每一行裡面相當於一個水平的ListView。
我只需要做一個Panel來佈局填充行就可以了。。垂直上面還是ListView自帶的效果
除此之後,還需要一個數據源來把 一維的數據改為了 二維的(根據每一行的個數)
直接上代碼:
FillRowViewSource這個類是把一個 一維的數據源改為了 二維的。主要方法是UpdateRowItems根據RowItemsCount把集合分割

private void UpdateRowItems() { if (sourceList == null) { return; } int i = 0; var rowItems = sourceList.Skip(i * RowItemsCount).Take(RowItemsCount); while (rowItems != null && rowItems.Count() != 0) { var rowItemsCount = rowItems.Count(); var item = this.ElementAtOrDefault(i); if (item == null) { item = new ObservableCollection<T>(); this.Insert(i, item); } for (int j = 0; j < rowItemsCount; j++) { var rowItem = rowItems.ElementAt(j); var temp = item.ElementAtOrDefault(j); if (temp==null || !temp.Equals(rowItem)) { item.Insert(j, rowItem); } } while (item.Count > rowItemsCount) { item.RemoveAt(item.Count - 1); } i++; rowItems = sourceList.Skip(i * RowItemsCount).Take(RowItemsCount); } var rowCount = sourceList.Count / RowItemsCount + 1; while (this.Count > rowCount) { this.RemoveAt(this.Count - 1); } }View Code
FillRowViewPanel 最早的時候希望使用item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));方式來獲得每個元素的大小,但是像Image這些控制項不是開始就有大小的,需要等待圖片載入完畢,而且每個Item都進行Measure的話。性能也不佳。
最後還是按照老控制項的方式IResizable 寫了個介面。綁定的數據源對象必須繼承這個。你需要告訴我你的每個Item的大小尺寸。這樣計算起來就方便多了而且有效多了

public class FillRowViewPanel : Panel { public int MinRowItemsCount { get { return (int)GetValue(MinRowItemsCountProperty); } set { SetValue(MinRowItemsCountProperty, value); } } // Using a DependencyProperty as the backing store for MinRowItemsCount. This enables animation, styling, binding, etc... public static readonly DependencyProperty MinRowItemsCountProperty = DependencyProperty.Register("MinRowItemsCount", typeof(int), typeof(FillRowViewPanel), new PropertyMetadata(0)); protected override Size MeasureOverride(Size availableSize) { var size = base.MeasureOverride(availableSize); return size; } protected override Size ArrangeOverride(Size finalSize) { double childrenWidth = 0; //double maxheight = double.MinValue; foreach (var item in Children) { if (item is ContentControl cc && cc.Content is IResizable iResizable) { //item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); var elementSize = iResizable; var width = elementSize.Width * finalSize.Height / elementSize.Height; //maxheight = Math.Max(elementSize.Height, maxheight); childrenWidth += width; } } double ratio = childrenWidth / finalSize.Width; double x = 0; var count = Children.Count; foreach (var item in Children) { if (item is ContentControl cc && cc.Content is IResizable iResizable) { var elementSize = iResizable; var width = elementSize.Width * finalSize.Height / elementSize.Height; //if children count is less than MinRowItemsCount and chidren total width less than finalwidth //it don't need to stretch children if (count < MinRowItemsCount && ratio < 1) { //to nothing } else { width /= ratio; } var rect = new Rect(x, 0, width, finalSize.Height); (item as FrameworkElement).Width = rect.Width; (item as FrameworkElement).Height = finalSize.Height; item.Arrange(rect); x += width; } } return base.ArrangeOverride(finalSize); } }View Code
在Sample頁面
原理就是Listview的Item其實是個ListView。而做為Item的ListView的Panel是FillRowViewPanel
記住給FillRowViewPanel的高度進行設置。相當於每個元素的高度

<ct:PullToRefreshGrid RefreshThreshold="100" PullToRefresh="PullToRefreshGrid_PullToRefresh"> <ListView x:Name="FillRowView" SizeChanged="FillRowView_SizeChanged"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="Margin" Value="0"/> <Setter Property="Padding" Value="0"/> </Style> </ListView.ItemContainerStyle> <ListView.ItemTemplate> <DataTemplate> <ListView ScrollViewer.HorizontalScrollMode="Disabled" ItemsSource="{Binding}"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="VerticalContentAlignment" Value="Stretch"/> <Setter Property="Margin" Value="0"/> <Setter Property="Padding" Value="0"/> </Style> </ListView.ItemContainerStyle> <ListView.ItemsPanel> <ItemsPanelTemplate> <ct:FillRowViewPanel x:Name="fillRowViewPanel" MinRowItemsCount="2" Height="300"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <!--<msct:ImageEx Margin="5" IsCacheEnabled="True" Source="{Binding ImageUrl}" Stretch="Fill"/>--> <Image Margin="5" Source="{Binding ImageUrl}" Stretch="Fill"/> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </ListView.ItemTemplate> </ListView> </ct:PullToRefreshGrid>View Code
在整個ListViewSizechanged的時候我們再根據自己的需求調整RowItemsCount。

private void FillRowView_SizeChanged(object sender, SizeChangedEventArgs e) { if (e.NewSize.Width < 600) { source.UpdateRowItemsCount(2); } else if (e.NewSize.Width >= 600 && e.NewSize.Width < 900) { source.UpdateRowItemsCount(3); } else { source.UpdateRowItemsCount(4); } }View Code
整個就差不多這樣了。做的比較簡單。一些東西也沒有全部去考慮。比如SourceList_CollectionChanged的時候。只考慮了Reset這種方式。
算是比較針對這個需求做的東東吧,如果有其他需求。可以提出來。大家一起擼擼。
老規矩 開源有益:Github