我們在使用像ListBox的列表控件時,我們都知道可以通過其ItemsPanel的依賴項屬性來自定義一個面板來放置列表控件中的列表項。除了CLR庫提供的幾個面板外,我們完全可以把自己寫的面板作為項列表的容器。
先給各位看看效果。
如何?效果還好吧?
面板的原理是這樣的:
1、從Panel類派出一個類,我命名為MyPanel。
2、重寫MeasureOverride方法,分別計算所有子元素的大小。
3、重寫ArrangeOverride方法,為每個子元素隨機生成X和Y坐標,然后再用這個隨機生的坐標來放置子元素。
4、為了能隔一段時間自動排版一次,我就加入了一個DispatcherTimer,並公開一個SwapInterval屬性,可以為調用者設置計時器的執行音隔,以秒為單位。
5、后來想想,如果每次都僅僅調用InvalidateArrange方法來重新排列子元素,好像有些枯燥,不如在重新排列之間弄一些動畫效果好看點。於是,我就在重新排列子元素之前讓面板“淡出”;當面板排列子元素完成后,再來個“淡入”效果,不錯。只是對Opacity屬性進行動畫處理就可以了,也不費事。
好,整體的思路就是這樣,然后把這個面板用到ListBox等列表控件的ItemsPanel上就行。
<ListBox.ItemsPanel> <ItemsPanelTemplate> <local:MyPanel SwapInterval="6"/> </ItemsPanelTemplate> </ListBox.ItemsPanel>
下面我貼一下整個MyPanel類的代碼,以供大家參考。(代碼進行了修訂)
public class MyPanel:Panel { Random rand = null; DispatcherTimer Timer = null; public MyPanel() { rand = new Random(); Timer = new DispatcherTimer(); Timer.Interval = TimeSpan.FromSeconds(SwapInterval); Timer.Tick += Timer_Tick; Timer.Start(); } #region 屬性 public static readonly DependencyProperty SwapIntervalProperty = DependencyProperty.Register("SwapInterval", typeof(double), typeof(MyPanel), new PropertyMetadata(10d, new PropertyChangedCallback(SwapIntervalChanged), new CoerceValueCallback(CoerceValCallback))); private static void SwapIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { MyPanel p = d as MyPanel; double sec = (double)e.NewValue; p.Timer.Stop(); p.Timer.Interval = TimeSpan.FromSeconds(sec); p.Timer.Start(); } private static object CoerceValCallback(DependencyObject d, object baseValue) { // 通過該方法測檢依賴項屬性的值 // 如果設置的值小於5秒,就自動改為5秒 double dv = (double)baseValue; if (dv < 5d) { dv = 5d; } return dv; } /// <summary> /// 切換動畫間隔,以秒為單位 /// </summary> public double SwapInterval { get { return (double)GetValue(SwapIntervalProperty); } set { SetValue(SwapIntervalProperty, value); } } #endregion void Timer_Tick(object sender, EventArgs e) { DoubleAnimation dat = new DoubleAnimation(); dat.From = 1d; dat.To = 0d; dat.Duration = TimeSpan.FromMilliseconds(900); dat.Completed += (sd, arg) => { // 當動畫完成時,面板的Opacity為0 // 此時重新排列子元素 this.InvalidateArrange(); DoubleAnimation datBack = new DoubleAnimation(); datBack.From = 0d; datBack.To = 1d; datBack.Duration = TimeSpan.FromSeconds(1); // 子元素排列完成后,再執行一個動畫 // 將Opacity再改為1 this.BeginAnimation(OpacityProperty, datBack); }; // 開始動畫,隱藏面板 this.BeginAnimation(OpacityProperty, dat); } protected override Size MeasureOverride(Size availableSize) { // 處理子元素的大小 foreach (UIElement u in InternalChildren) { // 一定要對每個子元素調用Measure // 不然,就看不到子元素了 u.Measure(availableSize); } // 如果不要求精確計算子元素占了多少 // 空間,可以直接返回0-0的Size,但不 // 要返回正無窮大,否則后果自負 return new Size(); } protected override Size ArrangeOverride(Size finalSize) { // 通過該方法對子元素進行排列 foreach (UIElement item in InternalChildren) { // 算出子元素的坐標的隨機值 double maxX = finalSize.Width - item.DesiredSize.Width; double maxY = finalSize.Height - item.DesiredSize.Height; if (maxX <=0d) { maxX = 1d; } if (maxY <= 0d) { maxY = 1d; } double X = rand.Next(0, (int)maxX); double Y = rand.Next(0, (int)maxY); // 調用Arrange方法安排子元素放在哪個位置 item.Arrange(new Rect(X, Y, item.DesiredSize.Width, item.DesiredSize.Height)); } return finalSize; } }
代碼可以移植到Windows Phone或Windows StoreApp中,原理都是一樣的。
其他代碼就沒有必要貼了,免得影響大家看帥哥美女,我把整個項目上傳就是了。