在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. 【iOS】Jenkins Gitlab持续集成打包平台搭建

    Jenkins Gitlab持续集成打包平台搭建 SkySeraph July. 18th 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph个人站点: ...

  2. jshint 一些选项(转载)

    内容来自: http://www.cnblogs.com/qianduanjingying/p/6185793.html 一些变量的作用: http://www.cnblogs.com/CloudMu ...

  3. 金融计算器app的下载情况

    下图就是一个多月来的下载量情况~话不多说,直接上图哈. 数据来源:百度开发者平台

  4. U-BOOT分析之:环境变量

    (环境如下:U-BOOT  S3C2440  LINUX) 记录自己的学习过程,如果分析有问题,请帮忙指正. 最近在研究U-BOOT的代码,其中的环境变量个人觉得用处非常大,所以重点学习和分析一下. ...

  5. memcache and redis 的区别

    memcache和redis都属于缓存但是memcache的存储大小是收到 限制的memcache的 键值长度是250,内存的大小限制是1M并且memcache不支持数据的持久化缓存 redis支持五 ...

  6. Ajax跨域Post方法调用Web Api(NuGet配置的环境)

    没安装的去NuGet安装, 然后安装, 建立Web Api项目.需要在里的WebApiConfig.cs里配置 config.EnableSystemDiagnosticsTracing(); //感 ...

  7. 为Python安装pymssql模块来连接SQLServer

    1.安装依赖包 yum install -y gcc python-devel 2.安装freetds 下载地址:http://pan.baidu.com/s/1pLKtFBl tar zxvf fr ...

  8. Linux 时间时区同步

    $ sudo cp /usr/share/zoneinfo/Asia/ShangHai /etc/localtime 上述是修改系统时区 同步时间 1.  安装ntpdate工具 $ sudo apt ...

  9. java cookie 工具类

    package com.xxx.xxx.xxx.xxx; import java.net.URLDecoder; import java.net.URLEncoder; import javax.se ...

  10. nginx android app 慢网络请求超时

    最近遇到了android 在慢网络下面请求服务器报 java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by ...