简介

在以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)

MeasureOverride方法

一言以蔽之,获取大小。

每个控件有提供给外部调用的Measure方法,用来决定该控件需要的空间。这个方法会对布局设置进行简单的处理,比如对Margin等属性进行预处理,然后把主要的步骤交给MeasureOverride方法。

这一方法的参数代表了该控件本身能拥有的大小。布局时需要考虑到它。

在这一方法中,控件需要做的就是遍历所有子控件,并调用他们的Measure方法,按照自己的布局方式对这些空间的大小进行运算。最后递归出一个总的空间大小,然后返回给它的父控件。

在这一过程中,按照需要,可能连子控件的位置信息也需要考虑(比如我们的瀑布流)。

所有的控件在计算完自己的所需控件后,会设置自己的DesiredSize属性,表明它所需的尺寸。这一属性在之后的Arrange过程中可以使用(不过不要在非自定义布局的情况下使用哦)。

此时控件和子控件的大小都已经确定了。

我们通过继承Panel来实现自己的瀑布流布局,这么做的目的,主要是可以将Panel用于ItemsControl及其子类的ItemsPanel属性(Panel类此时或许可以有另一个名字:LayoutPolicy)。配合ItemTemplate和ItemsSource,可以方便的填充和具象数据。

让我们看看如何实现一个这样行为的MeasureOverride:

protected override Size MeasureOverride(Size availableSize)
{
// 记录每个流的长度。因为我们用选取最短的流来添加下一个元素。
KeyValuePair<double, int>[] flowLens = new KeyValuePair<double, int>[];
foreach (int idx in Enumerable.Range(, ))
{
flowLens[idx] = new KeyValuePair<double, int>(0.0, idx);
} // 我们就用2个纵向流来演示,获取每个流的宽度。
double flowWidth = availableSize.Width / ; // 为子控件提供沿着流方向上,无限大的空间
Size elemMeasureSize = new Size(flowWidth, double.PositiveInfinity); foreach (UIElement elem in Children)
{
// 让子控件计算它的大小。
elem.Measure(elemMeasureSize);
Size elemSize = elem.DesiredSize; double elemLen = elemSize.Height;
var pair = flowLens[]; // 子控件添加到最短的流上,并重新计算最短流。
// 因为我们为了求得流的长度,必须在计算大小这一步时就应用一次布局。但实际的布局还是会在Arrange步骤中完成。
flowLens[] = new KeyValuePair<double, int>(pair.Key + elemLen, pair.Value);
flowLens = flowLens.OrderBy(p => p.Key).ToArray();
} return new Size(availableSize.Width, flowLens.Last().Key);
}

返回值是该元素本身实际需要的大小。

可看出我们也没有考虑缩放的问题。如果子控件要求的大小(特别是宽度)比流的宽度要大,就会导致显示不全的情况。这一点我们可以通过ViewBox来调整,不一定要在这个panel里实现(当然有特殊需求的除外)。

至此,panel和子控件的大小计算都已结束。

ArrangeOverride方法

Arrange,一言以蔽之,设置位置和大小。

这里的大小,就是通过Measure系列方法确定的DesiredSize。

在ArrangeOverride方法中,我们要做的,同样是遍历子控件,利用它们在Measure过程中确定的大小,来为它们加上位置信息。

可以看到,虽然我们的瀑布流panel在measure过程中也记录了位置信息,但只是用于计算总大小。而在arrange过程中,位置信息将被确实的利用上。

让我们看看ArrangeOverride方法的实现。对本例来说,它和MeasureOverride十分相似。

protected override Size ArrangeOverride(Size finalSize)
{
// 同样记录流的长度。
KeyValuePair<double, int>[] flowLens = new KeyValuePair<double, int>[]; double flowWidth = finalSize.Width / ; // 要用到流的横坐标了,我们用一个数组来记录(其实最初是想多加些花样,用数组来方便索引横向偏移。不过本例中就只进行简单的乘法了)
double[] xs = new double[]; foreach (int idx in Enumerable.Range(, ))
{
flowLens[idx] = new KeyValuePair<double, int>(0.0, idx);
xs[idx] = idx * flowWidth;
} foreach (UIElement elem in Children)
{
// 直接获取子控件大小。
Size elemSize = elem.DesiredSize;
double elemLen = elemSize.Height; var pair = flowLens[];
double chosenFlowLen = pair.Key;
int chosenFlowIdx = pair.Value; // 此时,我们需要设定新添加的空间的位置了,其实比measure就多了一个Point信息。接在流中上一个元素的后面。
Point pt = new Point(xs[chosenFlowIdx], chosenFlowLen); // 调用Arrange进行子控件布局。并让子控件利用上整个流的宽度。
elem.Arrange(new Rect(pt, new Size(flowWidth, elemSize.Height))); // 重新计算最短流。
flowLens[] = new KeyValuePair<double, int>(chosenFlowLen + elemLen, chosenFlowIdx);
flowLens = flowLens.OrderBy(p => p.Key).ToArray();
} // 直接返回该方法的参数。
return finalSize;
}

至此,整个流的布局都已经完成。

效果

让我们看看,这个瀑布流实现了怎样的效果。

我们先定义个结构,主要使用随机数来造成流中元素参差不齐的效果:

class MyItem
{
private double _height = double.NaN;
public double Height
{
get
{
if (double.IsNaN(_height))
{
Random r = new Random();
_height = + (r.NextDouble() - 0.5) * ;
}
return _height;
}
} public string Text
{
get; set;
}
}

ic是一个ItemsControl(也可以是其子类,如ListView。这样我们的panel就只负责布局,至于子控件的点击行为,动画行为,全部交给ListView)。

我们在UI事件中设置数据源:

ic.ItemsSource = Enumerable.Range(, ).Select(i => new MyItem { Text = i.ToString() });

XAML中对ItemsControl的设置如下。Border尝试占满其水平空间。同时所有的流内容可以上下滚动。

<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- 使用我们的自定义布局 -->
<local:MyPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10"
Height="{Binding Height}"
BorderBrush="Aqua"
BorderThickness="5"
HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Text}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

效果如下:

我们可以直接把ItemsControl换成ListView,再进行简单的Style设置,直接让我们的瀑布流与ListView的丰富特性融合:

<ListView x:Name="ic" SelectionMode="Multiple">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<local:MyPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border Margin="10"
Height="{Binding Height}"
BorderBrush="Aqua"
BorderThickness="5">
<TextBlock Text="{Binding Text}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

总结

这篇博客只是为大家介绍了一下对Measure和Arrange的简单尝试,但XAML中的控件却全部依赖这样的规则来完成布局。

每当大家遇到不同的控件组合达到的效果时,比如用Canvas可以让内容画在范围之外,StackPanel对其内容的处理等等,往往可以通过分析那个控件树的Measure和Arrange过程从中获得解答。

希望本文抛砖引玉,让UWP开发中出现更多有趣的设计和实现。

通过Measure & Arrange实现UWP瀑布流布局的更多相关文章

  1. WPF中的瀑布流布局(TilePanel)控件

    最近在用wpf做一个metro风格的程序,需要用到win8风格的布局容器,只能自己写一个了.效果如下 用法 : <local:TilePanel                          ...

  2. Xamarin自定义布局系列——瀑布流布局

    Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...

  3. JS瀑布流布局

    好久没有更新博客喽,今天来说一个瀑布流布局. 瀑布流在很多网站已有很多,现在只说一下简单的实现原理吧, 1.计算一行可以排放几个元素 2.建立一个数组用于存放第一行的每个元素的高度. 3.得到数组中的 ...

  4. CSS3学习总结——实现瀑布流布局与无限加载图片相册

    首先给大家看一下瀑布流布局与无限加载图片相册效果图: 一.pic1.html页面代码如下: <!DOCTYPE html> <html> <head> <me ...

  5. myWaterfall - jQuery瀑布流布局插件

    myWaterfall - jQuery瀑布流布局插件 Demo http://jsfiddle.net/q3011893/p5k2ogy8/embedded/result,html,css,js/ ...

  6. jquery实现简单瀑布流布局(续):图片懒加载

    # jquery实现简单瀑布流布局(续):图片懒加载 这篇文章是jquery实现简单瀑布流布局思想的小小扩展.代码基于前作的代码继续完善. 图片懒加载就是符合某些条件时才触发图片的加载.最常见的具体表 ...

  7. jquery实现简单瀑布流布局

    jquery实现简单瀑布流布局 是开头都会说的原理 瀑布流布局有两种,一种是固定列,一种是非固定列.在此主要记述第一种的实现. 固定列的特征是:无论页面如何缩放,每行的总列数都一致. 一行4列的瀑布流 ...

  8. JavaScript——基本的瀑布流布局及ajax动态新增数据

    本文用纯js代码手写一个瀑布流网页效果,初步实现一个基本的瀑布流布局,以及滚动到底部后模拟ajax数据加载新图片功能. 缺点: 1. 程序不是响应式,不能实时调整页面宽度: 2. 程序中当新增ajax ...

  9. flexbox实现不等宽不等高的瀑布流布局

    第一次做不等宽不等高的瀑布流布局,刚开始企图用ccs3的column属性+flexbox来实现,瞎捣鼓半天都没有能弄好, 弱鸡哭晕在厕所(┬_┬),气的午饭都没有吃. 后来逼着自己冷静下来,又捣鼓了1 ...

随机推荐

  1. js--webSocket入门

    Websocket 1.websocket是什么? WebSocket是为解决客户端与服务端实时通信而产生的技术.其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接, 此 ...

  2. js如何找到方法在哪个js文件

    在Console窗口中输入var f = methodA.prototype.constructor;console.log(f); 网络搜索到的方法.

  3. 删除所选项(附加搜索部分的jquery)

    1.视图端(views)的配置为: <script> $(document).ready(function() { $("#info-grid").kendoGrid( ...

  4. 1500. Prime Gap 11 月 11日

    /*本篇为转载,在此申明,具体就是先设定从2以后所有的数都为质数,定为质数的数的倍数则不是质数,慢慢排除后面的数*/ #include<iostream>#include<cstri ...

  5. hadoop显示ConnectionrRefused

    产生原因重启了服务器 (1)在安装目录/root/cloud/hadoop-2.2.0/ 重新hdfs namenode -format (2) 目录/root/cloud/hadoop-2.2.0/ ...

  6. Raab判别法确定级数是否收敛

  7. 批量创建SQL Server分区文件

    ) declare @i int set @table = 'v3_yqsd_report' begin exec('alter database '+@table+' add filegroup O ...

  8. WinForm程序关闭窗体后杀死进程

    调用this.Close();后监听Browser_FormClosed加作处理 this.FormClosed += new System.Windows.Forms.FormClosedEvent ...

  9. IIS出现问题时修改配置文件的几项说明

    近期系统在线运行经常出现object moved错误 通过查询资料,做了几项web.config文件的调整 1,调整应用程序池使用集成模式      <system.webServer>  ...

  10. Linux命令之awk数组使用范例

    目录 取ifconfig bond0的IP地址    1 命令如下:    2 统计apache日志单IP访问请求数排名    2 第一种方法    2 第二种方法    2 统计域名访问量    3 ...