瀑布流已經有點年代了吧,不過wp上還真是挺少資料的。今天抽空把本身以前搞過的東西寫出來,避免你們重複勞動。html
1、簡單的瀑布流排版加入ui虛擬化。ide
最近看了 段博瓊 ui虛擬化的一篇博文,連接:http://www.cnblogs.com/hebeiDGL/p/3410575.html優化
以爲還不錯,因而下載了他的demo稍微改了一下瀑布流效果。ui
demo截圖以下:this
主要改動:spa
1:自定義WaterFallPanel繼承Panel,用於實現瀑布流排版,並保持容器children距離頂部高度的信息:3d
public class WaterFallPanel : Panel { public WaterFallPanel() { /**默認2列**/ ColumnCount = 2; ColumnHeight = new double[ColumnCount]; childwidth = 480 / ColumnCount; } double childwidth; static double[] ColumnHeight; /// <summary> /// 列數 /// </summary> public int ColumnCount { get { return (int)this.GetValue(ColumnCountProperty); } set { this.SetValue(ColumnCountProperty, value); } } public static DependencyProperty ColumnCountProperty = DependencyProperty.Register("WaterFallPanel", typeof(int), typeof(WaterFallPanel), new PropertyMetadata(new PropertyChangedCallback((o, e) => { ColumnHeight = new double[(int)e.NewValue]; if (o == null || e.NewValue == e.OldValue) return; o.SetValue(ColumnCountProperty, e.NewValue); }))); protected override Size MeasureOverride(Size availableSize) { Size resultSize = new Size(0, 0); //List<DependencyObject> Children = this.GetVisualChildren().ToList(); for (int i = 0; i < Children.Count; i++) { Children[i].Measure(availableSize); } #region 測量值 for (int i = 0; i < ColumnHeight.Count(); i++) { ColumnHeight[i] = 0; } heightlist.Clear(); for (int i = 0; i < Children.Count; i++) { double miniheight = ColumnHeight.Min(); int h = 0; for (; h < ColumnHeight.Length; h++) { if (ColumnHeight[h] == miniheight) { break; } } ColumnHeight[h] += Children[i].DesiredSize.Height; heightlist.Add(ColumnHeight.Min()); } #endregion resultSize.Height = ColumnHeight.Max(); if (Children.Count == 0) { resultSize.Height = 0; resultSize.Width = 480; } else { resultSize.Width = (Children.Count + 1) * Children[0].DesiredSize.Width; } return resultSize; } protected override Size ArrangeOverride(Size finalSize) { for (int i = 0; i < ColumnHeight.Count(); i++) { ColumnHeight[i] = 0; } #region 排列值 heightlist.Clear(); for (int i = 0; i < Children.Count; i++) { double miniheight = ColumnHeight.Min(); int h = 0; for (; h < ColumnHeight.Length; h++) { if (ColumnHeight[h] == miniheight) { break; } } Children[i].Arrange(new Rect(new Point(Children[i].DesiredSize.Width * h, ColumnHeight[h]), Children[i].DesiredSize)); ColumnHeight[h] += Children[i].DesiredSize.Height; if (h == 0) { if (Children[i].DesiredSize.Width > childwidth) { ColumnHeight[h + 1] += Children[i].DesiredSize.Height; } } heightlist.Add(ColumnHeight.Min()); } if (Children.Count < ColumnCount) finalSize.Width = childwidth * (Children.Count + 1); else finalSize.Width = childwidth * (ColumnCount + 1); #endregion return finalSize; } private List<double> heightlist = new List<double>(); public double GetItemHeight(int item) { if (item >= 0 && item < heightlist.Count) { return heightlist[item]; } else { return 0; } } }
2:MainPage.xaml中修改itemspanlcode
<ItemsControl.ItemsPanel> <ItemsPanelTemplate> <LocalControl:WaterFallPanel x:Name="test" Loaded="test_Loaded"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>
3:MainPage.cs中修改虛擬化的計算方式:htm
void Visualizition() { // 自定義一個「字典」,用來保存每項的 Y座標 和它「自己」的引用 Dictionary<double, StackPanel> dic = new Dictionary<double, StackPanel>(); double height_sum = 0; for (int i = 0; i < listbox.Items.Count; i++) { // 由於 ListBox 是經過數據綁定生成的列表,因此須要經過 ItemContainerGenerator 得到 // 每一項的 StackPanel 這個父控件 FrameworkElement fe = listbox.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; StackPanel sp = FindFirstElementInVisualTree<StackPanel>(fe); dic.Add(height_sum, sp); // 累加 Y高度 if (height_sum <= waterpanel.GetItemHeight(i)) { height_sum = waterpanel.GetItemHeight(i)+1; } else { height_sum = waterpanel.GetItemHeight(i); } // 設置它的高度爲本身的實際高度 sp.Height = sp.ActualHeight; } // 每0.5秒鐘,循環檢查一次列表中,哪些項在屏幕內,若是在屏幕內,則顯示,若是 // 在屏幕外,則隱藏 Observable.Interval(TimeSpan.FromSeconds(.5)).ObserveOnDispatcher().Subscribe((_) => { foreach (var keyValue in dic) { if (((scrollViewer.VerticalOffset - 700) > keyValue.Key || keyValue.Key > (scrollViewer.VerticalOffset + 900))) { keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Collapsed; keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Collapsed; } else { keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Visible; keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Visible; } } }); }
其中WaterFallPanel的獲取用了比較奇葩的一個方式。blog
這樣作的好處是代碼簡單明瞭,適合新手瞭解ui虛擬化。很差的地方則是每次想實現瀑布流排版,都得手動寫入虛擬化計算代碼。代碼耦合太高。
2、經過自定義listbox實現瀑布流&虛擬化。下降代碼耦合度。
demo截圖:
一、自定義PowerListBox繼承listbox
public class PowerListBox:ListBox { // 自定義一個「字典」,用來保存每項的 Y座標 和它「自己」的引用 Dictionary<double, Border> dic = new Dictionary<double, Border>(); public PowerListBox() { DefaultStyleKey = typeof(PowerListBox); Loaded += PowerListBox_Loaded; Unloaded += PowerListBox_Unloaded; } private ScrollViewer _scrollView; private bool _isScrolling; void PowerListBox_Loaded(object sender, System.Windows.RoutedEventArgs e) { if (DesignerProperties.GetIsInDesignMode(this)) return; ApplyTemplate(); _scrollView.ScrollToVerticalOffset(_scrollView.VerticalOffset); var border = _scrollView.Descendants<Border>().FirstOrDefault(); Debug.Assert(border != null); var vsGroup = VisualStateManager.GetVisualStateGroups(border).OfType<VisualStateGroup>().FirstOrDefault(s => s.Name == "ScrollStates"); Debug.Assert(vsGroup != null); vsGroup.CurrentStateChanging += vsGroup_CurrentStateChanging; if (ItemsRootPanel != null && ItemsRootPanel.Children != null) { double height_sum=0; dic.Clear(); for (int i = 0; i < ItemsRootPanel.Children.Count; i++) { // 由於 ListBox 是經過數據綁定生成的列表,因此須要經過 ItemContainerGenerator 得到 // 每一項的 StackPanel 這個父控件 Border fe = VisualTreeHelper.GetChild(ItemsRootPanel.Children[i], 0) as Border; dic.Add(height_sum, fe); // 累加 Y高度 //var height = .GetItemHeight(i); if (height_sum <= (ItemsRootPanel as WaterFallPanel).GetItemHeight(i)) { height_sum = (ItemsRootPanel as WaterFallPanel).GetItemHeight(i) + 1; } else { height_sum = (ItemsRootPanel as WaterFallPanel).GetItemHeight(i); } // 設置它的高度爲本身的實際高度 fe.Height = fe.ActualHeight; } } } void PowerListBox_Unloaded(object sender, System.Windows.RoutedEventArgs e) { var border = _scrollView.Descendants<Border>().FirstOrDefault(); Debug.Assert(border != null); var vsGroup = VisualStateManager.GetVisualStateGroups(border).OfType<VisualStateGroup>().FirstOrDefault(s => s.Name == "ScrollStates"); Debug.Assert(vsGroup != null); vsGroup.CurrentStateChanging -= vsGroup_CurrentStateChanging; } public override void OnApplyTemplate() { base.OnApplyTemplate(); _scrollView = GetTemplateChild("ScrollViewer") as ScrollViewer; Debug.Assert(_scrollView != null); } void vsGroup_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) { if (e.NewState.Name == "NotScrolling") { _isScrolling = false; CompositionTarget.Rendering -= CompositionTarget_Rendering; } else { _isScrolling = true; CompositionTarget.Rendering += CompositionTarget_Rendering; } } void CompositionTarget_Rendering(object sender, EventArgs e) { RefreshDisplayArea(); } void RefreshDisplayArea() { foreach (var keyValue in dic) { if (((_scrollView.VerticalOffset - 20) > keyValue.Key || keyValue.Key > (_scrollView.VerticalOffset + 600))) { keyValue.Value.Child.Visibility = Visibility.Collapsed; } else { keyValue.Value.Child.Visibility = Visibility.Visible; } } //for (int i = 0; i < ItemsRootPanel.Children.Count; i++) //{ // var item = GetItem(i); // var height = (ItemsRootPanel as WaterFallPanel).GetItemHeight(i) ; // if (((_scrollView.VerticalOffset - 20) > height || height > (_scrollView.VerticalOffset + 607))) // { // FrameworkElement fe = VisualTreeHelper.GetChild(item, 0) as FrameworkElement; // fe.Visibility = Visibility.Collapsed; // //keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Collapsed; // //keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Collapsed; // } // else // { // FrameworkElement fe = VisualTreeHelper.GetChild(item, 0) as FrameworkElement; // fe.Visibility = Visibility.Visible; // //keyValue.Value.Children[0].Visibility = System.Windows.Visibility.Visible; // //keyValue.Value.Children[1].Visibility = System.Windows.Visibility.Visible; // } //} } private ListBoxItem GetItem(int index) { if (index < 0 || index >= Items.Count) return null; if (ItemsSource != null) { var isVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(this); if (!isVirtualizing) { Debug.Assert(ItemsRootPanel != null); return ItemsRootPanel.Children[index] as ListBoxItem; } Debug.Assert(ItemContainerGenerator != null); return ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem; } return Items[index] as ListBoxItem; } #region FirstVisibleIndex public static readonly DependencyProperty FirstVisibleIndexProperty = DependencyProperty.Register("FirstVisibleIndex", typeof(int), typeof(PowerListBox), new PropertyMetadata(0)); public int FirstVisibleIndex { get { return (int)GetValue(FirstVisibleIndexProperty); } private set { SetValue(FirstVisibleIndexProperty, value); } } #endregion #region LastVisibleIndex public static readonly DependencyProperty LastVisibleIndexProperty = DependencyProperty.Register("LastVisibleIndex", typeof(int), typeof(PowerListBox), new PropertyMetadata(0)); public int LastVisibleIndex { get { return (int)GetValue(LastVisibleIndexProperty); } private set { SetValue(LastVisibleIndexProperty, value); } } #endregion private Panel _itemsRootPanel; private Panel ItemsRootPanel { get { if (_itemsRootPanel == null && ItemsPresenter != null) { _itemsRootPanel = ItemsPresenter.ChildrenEx().FirstOrDefault() as Panel; } return _itemsRootPanel; } } private ItemsPresenter _itemsPresenter; private ItemsPresenter ItemsPresenter { get { return _itemsPresenter ?? (_itemsPresenter = _scrollView.Descendants<ItemsPresenter>().FirstOrDefault()); } } }
其餘見demo。
備註:
一、虛擬化過程當中:注意容器的高度爲本身的實際高度
// 設置它的高度爲本身的實際高度
// 很重要,若是不爲父容器指定固定高度,當子元素隱藏後,父容器高度變爲0px sp.Height = sp.ActualHeight;
二、計算虛擬化區域有不少種方法,demo中只是簡單粗暴的計算,有不少能夠進行優化。
三、瀑布流排版只是見當實現兩行的,須要多行的話請自行改動,詳見附加連接,固然林政的瀑布流排版有點問題,改改更健康。
四、demo2中 Dictionary<double, Border> dic的表只是在加載過程當中進行一次初始化,在listbox動態加載過程當中並無修改,爲此listbox可將虛擬化計算改成
void RefreshDisplayArea() { for (int i = 0; i < ItemsRootPanel.Children.Count; i++) { var item = GetItem(i); Border fe = VisualTreeHelper.GetChild(item, 0) as Border; fe.Height = fe.ActualHeight; var height = (ItemsRootPanel as WaterFallPanel).GetItemHeight(i); if (((_scrollView.VerticalOffset - fe.Height ) > height || height > (_scrollView.VerticalOffset + 820))) { fe.Child.Visibility = Visibility.Collapsed; } else { fe.Child.Visibility = Visibility.Visible; } } }
其餘代碼敬請發揮。
demo連接:
1:http://files.cnblogs.com/fatlin/VirtualizationListBoxDemo.rar
2:http://files.cnblogs.com/fatlin/TextListbox.rar
其餘相關連接:
瀑布流:http://www.cnblogs.com/Smallcode/archive/2012/10/19/2730810.html