windowsphone 瀑布流&ui虛擬化

瀑布流已經有點年代了吧,不過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;
            }
        }
    }
View Code

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;
                        }
                    }
                });
        }
View Code

  其中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()); }
        }

    }
View Code

其餘見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;
                }
            }

        }
View Code

    其餘代碼敬請發揮。

 

 

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

相關文章
相關標籤/搜索