在以XAML为主的控件布局体系中,有用于完成布局的核心步骤,分别是measure和arrange。继承体系中由UIElement类提供Measure和Arrange方法,并由其子类FrameworkElement类提供protected的MeasureOverride和ArrangeOverride方法来为自定义控件提供实现自定义布局的接口。本文通过一个瀑布流布局实现来为大家简单地介绍这两个核心方法。
所谓瀑布流布局,是多列布局的一种形式,列中元素等比缩放使得自身与列等宽,每列再以StackPanel的形式布局,下一个元素自动排布到最短的那一列上。
大致效果可以参考百度图片首页,点击“摄影”,“美食”或“宠物”后进入的页面效果。(宠物here:http://image.baidu.com/channel?c=%E5%AE%A0%E7%89%A9&t=%E5%85%A8%E9%83%A8&s=0)
一言以蔽之,获取大小。
每个控件有提供给外部调用的Measure方法,用来决定该控件需要的空间。这个方法会对布局设置进行简单的处理,比如对Margin等属性进行预处理,然后把主要的步骤交给MeasureOverride方法。
这一方法的参数代表了该控件本身能拥有的大小。布局时需要考虑到它。
在这一方法中,控件需要做的就是遍历所有子控件,并调用他们的Measure方法,按照自己的布局方式对这些空间的大小进行运算。最后递归出一个总的空间大小,然后返回给它的父控件。
在这一过程中,按照需要,可能连子控件的位置信息也需要考虑(比如我们的瀑布流)。
所有的控件在计算完自己的所需控件后,会设置自己的DesiredSize属性,表明它所需的尺寸。这一属性在之后的Arrange过程中可以使用(不过不要在非自定义布局的情况下使用哦)。
此时控件和子控件的大小都已经确定了。
我们通过继承Panel来实现自己的瀑布流布局,这么做的目的,主要是可以将Panel用于ItemsControl及其子类的ItemsPanel属性(Panel类此时或许可以有另一个名字:LayoutPolicy)。配合ItemTemplate和ItemsSource,可以方便的填充和具象数据。
让我们看看如何实现一个这样行为的MeasureOverride:
返回值是该元素本身实际需要的大小。
可看出我们也没有考虑缩放的问题。如果子控件要求的大小(特别是宽度)比流的宽度要大,就会导致显示不全的情况。这一点我们可以通过ViewBox来调整,不一定要在这个panel里实现(当然有特殊需求的除外)。
至此,panel和子控件的大小计算都已结束。
Arrange,一言以蔽之,设置位置和大小。
这里的大小,就是通过Measure系列方法确定的DesiredSize。
在ArrangeOverride方法中,我们要做的,同样是遍历子控件,利用它们在Measure过程中确定的大小,来为它们加上位置信息。
可以看到,虽然我们的瀑布流panel在measure过程中也记录了位置信息,但只是用于计算总大小。而在arrange过程中,位置信息将被确实的利用上。
让我们看看ArrangeOverride方法的实现。对本例来说,它和MeasureOverride十分相似。
至此,整个流的布局都已经完成。
让我们看看,这个瀑布流实现了怎样的效果。
我们先定义个结构,主要使用随机数来造成流中元素参差不齐的效果:
ic是一个ItemsControl(也可以是其子类,如ListView。这样我们的panel就只负责布局,至于子控件的点击行为,动画行为,全部交给ListView)。
我们在UI事件中设置数据源:
XAML中对ItemsControl的设置如下。Border尝试占满其水平空间。同时所有的流内容可以上下滚动。
效果如下:

我们可以直接把ItemsControl换成ListView,再进行简单的Style设置,直接让我们的瀑布流与ListView的丰富特性融合:
这篇博客只是为大家介绍了一下对Measure和Arrange的简单尝试,但XAML中的控件却全部依赖这样的规则来完成布局。
每当大家遇到不同的控件组合达到的效果时,比如用Canvas可以让内容画在范围之外,StackPanel对其内容的处理等等,往往可以通过分析那个控件树的Measure和Arrange过程从中获得解答。
希望本文抛砖引玉,让UWP开发中出现更多有趣的设计和实现。