在Windows10 UWP开发平台上内置的XMAL布局面板包括RelativePanelStackPanelGridVariableSizedWrapGridCanvas。在开发淘宝UWP应用时,遇到以下业务场景。

业务场景


场景一:淘宝商品提供的一些消费者保障服务

场景二:淘宝商品的SKU属性展示

实现分析


系统默认的面板容器控件显然不符合要求了。在WPF里面有WrapPanel,但是在UWP应用里面没有,这个时候就需要自定义个Panel了来实现WrapPanel的功能,实现起来不是很复杂。在MSDN的文档上已经给出了详细的实现说明:Xaml自定义面板,主要就是自定义一个Panel的派生类,然后重写(MeasureOverrideArrangeOverride)方法。

以下是MSDN上对两个方法的解释说明

MeasureOverride方法

MeasureOverride 方法有返回值,当 Measure 方法在面板上受到布局中的父元素调用时,布局系统将使用该值作为面板自身的起始 DesiredSize。方法内的逻辑选择与它返回的内容同等重要,而且逻辑经常影响返回的值。

所有 MeasureOverride 实现应当循环访问 Children,并且对每个子元素调用 Measure 方法。调用 Measure 方法可为DesiredSize 属性创建值。这可能会通知面板本身需要多少空间,以及如何在元素间划分空间或为特定的子元素调整大小。

以下是 MeasureOverride 方法非常基本的框架:

protected override Size MeasureOverride(Size availableSize)
{
Size returnSize; //TODO might return availableSize, might do something else //loop through each Child, call Measure on each
foreach (UIElement child in Children)
{
child.Measure(new Size()); // TODO determine how much space the panel allots for this child, that's what you pass to Measure
Size childDesiredSize = child.DesiredSize; //TODO determine how the returned Size is influenced by each child's DesiredSize
//TODO, logic if passed-in Size and net DesiredSize are different, does that matter?
}
return returnSize;
}

ArrangeOverride方法

ArrangeOverride 方法有 Size 返回值,当 Arrange 在面板上受到布局中的父元素调用时,布局系统将在呈现面板本身时使用该值。通常输入 finalSize 和 ArrangeOverride 返回的 Size 相同。如果不相同,这意味着面板正尝试将自己调整为不同的大小,而不是布局中的其他参与者声明可用的大小。最终大小基于之前已通过面板代码运行布局的度量传递,这是通常不返回不同大小的原因:这意味着你在故意忽略度量逻辑。

不要返回具有 Infinity 组件的 Size。尝试使用这样的 Size 将从内部布局引发异常。

所有 ArrangeOverride 实现应当循环访问 Children,并且对每个子元素调用 Arrange 方法。和 Measure 一样,Arrange 没有返回值。与 Measure 不同,经计算的属性不会设置为结果(但是, 问题中的元素通常引发 LayoutUpdated 事件)。

以下是 ArrangeOverride 方法非常基本的框架:

protected override Size ArrangeOverride(Size finalSize)
{
//loop through each Child, call Arrange on each
foreach (UIElement child in Children)
{
Point anchorPoint = new Point(); //TODO more logic for topleft corner placement in your panel
// for this child, and based on finalSize or other internal state of your panel
child.Arrange(new Rect(anchorPoint, child.DesiredSize)); //OR, set a different Size
}
return finalSize; //OR, return a different Size, but that's rare
}

创建自定义Panel控件


下面用一个简单的demo演示一下,就知道这两个方法的作用了。

首先新建一个MyPanel类继承自Panel类,将Mypanel的背景色设置成灰色,在MyPanel里面放入6个Border控件,每个Border控件设置不同的背景颜色,固定Width和Height为100或者200,方便查看各个控件的大小区域。

UI xaml:

<Page
x:Class="AppArrange.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppArrange"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:MyPanel Background="Gray" HorizontalAlignment="Left" VerticalAlignment="Top">
<Border Background="Red" Width="100" Height="100">
<TextBlock Text="1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
<Border Background="Green" BorderThickness="1" Width="100" Height="200">
<TextBlock Text="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
<Border Background="Yellow" Width="200" Height="100">
<TextBlock Text="3" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
<Border Background="OrangeRed" Width="100" Height="100">
<TextBlock Text="4" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
<Border Background="Orange" Width="100" Height="100">
<TextBlock Text="5" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
<Border Background="Orchid" Width="100" Height="100">
<TextBlock Text="6" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
</Border>
</local:MyPanel>
</Grid>
</Page>

Code behind:

public class MyPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
} protected override Size ArrangeOverride(Size finalSize)
{
return base.ArrangeOverride(finalSize);
}
}

这个时候MeasureOverride和ArrangeOverride什么都没有做,如果直接运行会是什么样子呢?

这个时候界面就是一片空白。添加的6个Border控件没有显示。Mypanel的灰色背景也没有显示,说明Mypanel的size是0,没有显示出来。

下面来实现MeasureOverride方法,遍历每个子控件并调用Measure方法。

public class MyPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
foreach (FrameworkElement child in Children)
{
child.Measure(availableSize);
}
return availableSize; } protected override Size ArrangeOverride(Size finalSize)
{
return base.ArrangeOverride(finalSize);
}
}

这个时候Mypanel的面板显示出来了,背景颜色是灰色,但是子控件没有显示。

接下来,实现ArrangeOverride方法

protected override Size ArrangeOverride(Size finalSize)
{
double x = ;
double y = ; foreach (FrameworkElement child in Children)
{
child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
x += child.DesiredSize.Width;
y += child.DesiredSize.Height; }
return finalSize;
}

运行结果:

子控件出来了,超出Page范围的会被遮住。这个时候就可以根据需要定义每个子控件的(x,y)坐标进行布局了。如果要横向布局,就递增x坐标,如果纵向布局就递增y坐标。

横向布局

递增x坐标

protected override Size ArrangeOverride(Size finalSize)
{
double x = ;
double y = ; foreach (FrameworkElement child in Children)
{
child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
x += child.DesiredSize.Width;
// y += child.DesiredSize.Height; }
return finalSize;
}

运行结果

纵向布局

递增y坐标

protected override Size ArrangeOverride(Size finalSize)
{
double x = ;
double y = ; foreach (FrameworkElement child in Children)
{
child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
//x += child.DesiredSize.Width;
y += child.DesiredSize.Height; }
return finalSize;
}

运行结果:

接下来的问题,就是横向或者纵向布局的时候,判断何时该换行了。要换行就要计算依次排列的子控件的宽和高,同时和Mypanel的大小进行比较是否超出边界。

这其中还有个问题是MeasureOverrideArrangeOverride 都会返回一个size大小。这两个大小有什么不一样吗。

MeasureOverride 返回值:此对象在布局过程中基于其对子对象分配大小的计算或者基于固定容器大小等其他因素而确定的它所需的大小。

ArrangeOverride 返回值:元素在布局中排列后使用的实际大小。

MeasureOverride的输入size和返回size

可以做几个实验看看实际效果:

1. 如果给Measure传递的值较小,例如比最小的子控件还小:

protected override Size MeasureOverride(Size availableSize)
{
foreach (FrameworkElement child in Children)
{
child.Measure(new Size(,));
}
return availableSize;
}

运行结果

子控件会被裁剪部分区域,显示不完整,以适应较小的size。

2. 如果给Measure传递的值较大,比子控件的大小要大。

protected override Size MeasureOverride(Size availableSize)
{
foreach (FrameworkElement child in Children)
{
child.Measure(new Size(,));
}
return availableSize;
}

运行结果

结果显示还是正常的大小,没有变化。

总结:

说明子控件的DesiredSize的会受到Measure传递的大小的限制,过小就会被裁剪,过大,不受影响,以实际的DesiredSize显示。Measure方法就是给控件分配一个可以显示的大小范围。

下面看看MeasureOverride返回值,实际上和Measure的作用是一样的,给MeasureOverride方法的参数size是MyPanel的父控件给MyPanel分配的显示区域大小,实际上会受到MyPanel 的Width、Height、HorizontalAlignment、VerticalAlignment等设置的影响,这里就不展开了。

如果子控件的大小显示区域超过了MyPanel的父控件给MyPanel分配的显示区域大小,子控件的显示区域会被裁剪。这个时候可以根据业务需要调整MeasureOverride 返回size或者调整每个子控件的Measure输入size,缩小子控件,使每个子控件都完整显示出来。

下面看看如果设置的MeasureOverride返回值过小是什么效果

protected override Size MeasureOverride(Size availableSize)
{
foreach (FrameworkElement child in Children)
{
child.Measure(availableSize);
}
return new Size(,);
}

运行结果:

灰色区域是容器的大小,各个子控件已经超出容器控件的大小范围了,MyPanel的父控件分配的大小是整个page的大小,在遍历子控件Border时分配给子控件也是page的大小显示区域,但是每个子控件都设置了Width和Height,而child.Measure(availableSize)的大小比子控件自己的size大,所以最后会显示控件实际的大小,不会受child.Measure(availableSize)的影响,但是MyPanel的MeasureOverride最后返回的时候,size被修改小了,这样整个容器就显示被修改后的大小,子控件溢出边界。

所以在自定义容器控件的时候,MeasureOverride 方法返回的size应该是所有子控件显示区域的最小size。而计算显示区域的最小size,应该根据子控件的布局方式来判断。

ArrangeOverride的输入size和返回size

需要注意的是:通常情况下ArrangeOverride输入的size和返回的size一样,如果返回的size过小,也会遇到和MeasureOverride返回的size过小一样的显示问题。所以不建议修改ArrangeOverride的返回size,直接将输入size返回就可以了,ArrangeOverride方法主要作用是给子控件定位坐标和大小,完成布局。

有了以上的准备,应该就知道怎么实现淘宝的业务了,主要是在水平方向上依次排列子控件,然后自动换行。

首先在MeasureOverride里面计算子控件需要显示大小区域,在ArrangeOverride里面根据子控件的大小排列方式计算显示坐标,实现自动换行。

protected override Size ArrangeOverride(Size finalSize)
{
double x = ;
double y = ;
double maxHeight = ; foreach (FrameworkElement child in Children)
{
if (maxHeight < child.DesiredSize.Height)
{
maxHeight = child.DesiredSize.Height;
} if ((x + child.DesiredSize.Width) > finalSize.Width)
{
x = ;
y += maxHeight;
maxHeight = ;
} child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
x += child.DesiredSize.Width;
}
return finalSize;
}

运行结果:

如果要实现更复杂的功能,例如要同时支持可以横向纵向排列子控件,就需要做一些封装了,这里就不一一展开了,最后附上有完整功能的WrapPanel实现代码供大家参考。可以实现横向和纵向排列。

我的面板我做主 -- 淘宝UWP中自定义Panel的实现的更多相关文章

  1. 飞流直下的精彩 -- 淘宝UWP中瀑布流列表的实现

    在淘宝UWP中,搜索结果列表是用户了解宝贝的重要一环,其中的图片效果对吸引用户点击搜索结果,查看宝贝详情有比较大的影响.为此手机淘宝特意在搜索结果列表上采用了2种表现方式:一种就是普通的列表模式,而另 ...

  2. 淘宝UWP中的100个为什么

    从淘宝UWP第一版发布到现在,已经有十个月了,期间收到了用户各种各样的反馈,感谢这些用户的反馈,指导我们不断的修正.完善应用.但是也有一部分需求或建议,由于资源或技术的限制,目前确实无法做到,只能对广 ...

  3. 从淘宝 UWP 的新功能 -- 比较页面来谈谈 UWP 的窗口多开功能

    前言 之前在 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记 这篇随笔中介绍了一下 UWP 淘宝的“比较”新功能呱呱坠地的过程.在鲜活的文字背后,其实都是程序员不眠不休的血泪史(有血有泪有史) ...

  4. 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记

    前言 网购已经不再是现在的时髦,而变成了我们每天的日常生活.上网已经和买买买紧密地联系在了一起,成为了我们的人生信条.而逛街一词,越来越多地变成了一种情怀.有时候我们去逛街,要么是为了打发时间,要么是 ...

  5. 淘宝UWP桌面版已经发布

    目前正在等待应用商店的检测,很快会可以下载. 谢谢各位园主针对淘宝UWP 桌面版(又叫PC版,HD版等等)给予的feedback,在这里统一回复一下,就不一一感谢了. 有一件事需要说明一下,请看下图: ...

  6. PC版淘宝UWP揭秘

    经过第一轮内测后的bug数量:65 2015/11/27 - bug数量 = 60 2015/11/30 - bug数量 = 53 2015/12/1 - bug数量 = 49 2015/12/2 - ...

  7. 手机淘宝UWP

    各位园主好! bug 走势: 哪天bug 足够少,哪天就可以发布了  :) 2015/10/23: 49 2015/10/26: 40 2015/10/27: 36 2015/10/28: 30 20 ...

  8. php提取淘宝URL中ID的代码

    一段可以提取淘宝URL中ID的PHP代码. 例如: <?php $taobao = 'taobao.com'; $tmall = 'tmall.com'; $guojitmall = 'tmal ...

  9. Customize Acrylic Brush in UWP Applications(在UWP中自定义亚克力笔刷)

    原文 Customize Acrylic Brush in UWP Applications(在UWP中自定义亚克力笔刷) Windows 10 Fall Creators Update(Build ...

随机推荐

  1. 前端试题本(HTML+CSS篇)

    CS1. 下面关于IE.FF下面CSS的解释区别描述正确的有?(不定项)CS2请选出结构正确的选项CS3.下面哪些是HTML5 新增的表单元素?CS4在使用table表现数据时,有时候表现出来的会比自 ...

  2. UWP Composition API - 锁定列的FlexGrid

    需求是第一列锁定,那么怎么让锁定列不跟着滚动条向做移动呢? 其实很简单,让锁定列跟scrollviewer的滚动做反方向移动. 先看一下这个控件的模板,嗯,其实很简单,就是ListView的模板,不同 ...

  3. python 小程序 比较目录间的差异

    比较目录间的差异: I 只按照名称做了比较,如果目录的文件名称相同,但是内容不同脚本认为为相同文件 II 针对目录下面的目录没有循环比较,只是比较了目录的名称 import sys, os def d ...

  4. LINUX 忘记root密码如何修改

    重启linux系统 3 秒之内要按一下回车,出现如下界面 然后输入e 在 第二行最后边输入 single,有一个空格.具体方法为按向下尖头移动到第二行,按"e"进入编辑模式 在后边 ...

  5. Swift 之模糊效果(毛玻璃效果,虚化效果)的实现

    前言: 之前项目中有用到过Objective-C的的模糊效果,感觉很是不错,而且iOS8之后官方SDK也直接提供了可以实现毛玻璃效果的三个类:UIBlurEffect.UIVibrancyEffect ...

  6. oracle(sql)基础篇系列(四)——数字字典、索引、序列、三范式

      数字字典表 --查看当前用户下面有哪些张表 select * from user_tables; select table_name from user_tables;   --查看当前用户下面有 ...

  7. Unity5中WebGL平台封装的一些技巧

    最近在接触unity的WebGL平台,其实这个平台作为Web Player的替代品,已经能满足大部分的开发需求,而且不需要额外的插件支持,确实方便了不少,但开发中依旧遇到了不少问题,在这里记录和共享一 ...

  8. iOS 常用第三方类库、完整APP示例

    一.第三方类库 1:基于响应式编程思想的oc地址:https://github.com/ReactiveCocoa/ReactiveCocoa2:hud提示框地址:https://github.com ...

  9. docker学习之二镜像创建

    继上一篇docker入门之后写一点使用的经验. 通过命令:docker run -it REPOSITORY或IMAGE ID   注:-it后面跟的字段可以通过下面指令获得 创建运行的容器,会进入一 ...

  10. 2016-2017 ACM-ICPC Asia-Bangkok Regional Contest

    A. WSI Extreme 将人按洗澡时间从大到小排序,那么$ans=\sum_{i=1}^{n}a_i\times\lfloor\frac{i+W-1}{W}\rfloor$. 当$W$比较大时, ...