我們在使用像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中,原理都是一樣的。
其他代碼就沒有必要貼了,免得影響大家看帥哥美女,我把整個項目上傳就是了。
下載下傳位址:https://files.cnblogs.com/tcjiaan/MyLayoutPanel.zip