最近又在搞点小东西,美化界面的时候发现瀑布流效果比较不错.顺便就搬到了WPF,下面是界面
我对WEB前端不熟,JS和CSS怎么实现的,我没去研究过,这里就说下WPF的实现思路,相当简单.
1.最重要的就是每个子项的顺序填充,我是把界面看做N列,然后在每列里依次加载子项.最后结果就是,界面放一个Uniform,设置Columns,再添加几个ItemsControl.
2.添加Item的时候,判断每个ItemsControl的实际高度,把子项添加到最小的那个ItemsControl,这样避免了某一列拉得很长.
3.再做一层封装,就变成了一个支持Binding的WaterfallControl.
这里上几段控件的源码,供参考:
1.WaterfallControl.cs
[TemplatePart(Name = "grdRoot", Type = typeof(UniformGrid))]
public class WaterfallControl : ItemsControl
{
private UniformGrid grdRoot; private List<ItemsControl> itemsContorls; public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
} // Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(WaterfallControl), new PropertyMetadata(, OnColumnsChanged)); private static void OnColumnsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
int columns = (int)e.NewValue;
if (columns < )
{
throw new ArgumentOutOfRangeException("Columns");
}
var control = sender as WaterfallControl;
control.Columns = columns;
control.InitPanel();
} public WaterfallControl()
{
this.Loaded += WaterfallControl_Loaded;
this.itemsContorls = new List<ItemsControl>();
} void WaterfallControl_Loaded(object sender, RoutedEventArgs e)
{
this.InitPanel();
} private void InitPanel()
{
if (!this.IsLoaded)
{
return;
} grdRoot.Children.Clear();
itemsContorls.Clear();
for (var i = ; i < this.Columns; i++)
{
var ic = new ItemsControl();
ic.ItemTemplate = this.ItemTemplate;
ic.VerticalAlignment = System.Windows.VerticalAlignment.Top;
grdRoot.Children.Add(ic);
itemsContorls.Add(ic);
} if (this.ItemsSource != null)
{
var enumerator = this.ItemsSource.GetEnumerator();
while (enumerator.MoveNext())
{
this.AddChild(enumerator.Current);
}
}
} public override void OnApplyTemplate()
{
base.OnApplyTemplate();
grdRoot = (UniformGrid)this.GetTemplateChild("grdRoot");
} protected override void AddChild(object value)
{
var ic = itemsContorls.OrderBy(t => t.ActualHeight).FirstOrDefault();
ic.Items.Add(value);
ic.UpdateLayout();
} protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove)
{
var enumerator = e.NewItems.GetEnumerator();
while (enumerator.MoveNext())
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
this.AddChild(enumerator.Current);
}
else
{
foreach (var ic in this.itemsContorls)
{
ic.Items.Remove(enumerator.Current);
}
}
}
}
}
}
2.WaterfallControl的样式
<Style TargetType="controls:WaterfallControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:WaterfallControl">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<UniformGrid Name="grdRoot" Columns="{TemplateBinding Columns}"> </UniformGrid>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
3.调用
WaterfallControl继承自ItemsControl,所以和ItemsControl的使用没有区别,只需要额外指定一个Columns即可.
可能遇到的问题:
1.遇到图片不能直接计算高度,可能导致某列很长.可以用扩展属性给图片指定一个初始占位高度.
2.遇到界面大小变化,列数是不是应该动态变化,这个要实现也简单,监视下Window.SizeChanged,然后改变Columns就行了.
3.我直接把ScrollViewer放到WaterfallControl的模板里了,建议抽出来,监视下滚动事件,实现滚动到底加载数据.
4.不知道是否有更简单明了的方法.