65.9K
CodeProject 正在变化。 阅读更多。
Home

使用传送带动画面板进行 Google 图片搜索

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (4投票s)

2011 年 12 月 9 日

CPOL

9分钟阅读

viewsIcon

14893

downloadIcon

692

在一个网页中执行四种不同的谷歌图片搜索,每个搜索结果都以“传送带”形式显示和管理。

注意

此代码示例需要以下安装才能运行和编译

因为它基于这两个来源的特性!

引言

有一段时间,我一直想编写一个传送带显示。
我“玩过”许多种“多元素”显示,发现其中许多都有太多与 UI 相关的故障。

传送带显示具有一些“内置”优点

  • 很好地适应矩形定义的区域
  • 元素具有不同的大小 - 接近、更相关的元素更大,因此是信息相关性导向的
  • 用户对项目数量有绝对的概念
  • 用户对元素有持续的定位
  • 物品沿“传送带”的移动相对优雅(在我对不同多物品 UI 显示的实验中,我注意到如果一个或多个物品移动速度快于其余物品,它实际上会“刺激眼睛”)

最终促使我坐下来编写这个显示器有两件事

所以我坐下来编写了一个“传送带”显示动画面板...

背景

为什么是面板(甚至更重要的是——为什么是动画)?

实际上,所有 WPF/Silverlight 开箱即用的面板都是“静态内容排列器”有一些很好的理由。主要是,这使得它们保持基本,从而通用。

促使我推翻这个惯例的原因如下

  • 为什么不呢?没有人说面板不应该具有动画效果。
  • 在这种特殊情况下 - 如果传送带没有实际转动,将面板的子级沿传送带路径散布就没有意义。
  • 在这种特殊情况下 - 传送带的转动意味着与面板子级顺序(zorder)的密切交互,该顺序在面板内部进行管理。
  • 封装 - 一个独立的面板,具有内置功能,无需外部代码操作。
  • 我个人希望编写一个可动画的自定义传送带面板。

可控可绑定面板

我遇到的第一个障碍是“绑定面板”控制问题,在 **Dr. WPF** 的精彩文章 概念子级:WPF 中一个强大的新概念 中解释和解决。
简而言之:当您希望面板显示绑定源中的项目时,您通常将面板设置为某种 ItemsControlListBox* 或 ItemsControl)的 ItemsPanel 属性,然后将 ItemsControlItemsSource 属性绑定到您的逻辑项目列表(在使用 **MVVM** 模式时通常在 **ViewModel** 中)。
然后 ItemsControl(理所当然地)对面板的子级拥有**独占**控制权,因此禁止通过面板(甚至在面板内部)操作子级!
虽然 **Dr. WPF** 使用了一些巧妙的“技巧”来克服这个障碍并“控制不可控”,但我采取了完全不同的方法...

从不同的角度看问题,我们基本上想要的是将面板的子级绑定到逻辑项目源。
我们被迫使用 ItemsControl 作为逻辑项目源和面板子级之间的**中介**(因此失去了对面板子级的控制)。
如果我们能有一个不那么“控制狂”的**中介**,或者甚至是一个面板拥有一个“ItemsSource”属性,那该有多好?
嗯,我们可以,使用 AttachedProperty

<ConvbeltPanel:AnimConvbeltPanel x:Name="AnimConvbeltPanel" 
    ConvbeltPanel:PanelChildrenBinder.ItemsSource ="{Binding Path=Images}" 
    ConvbeltPanel:PanelChildrenBinder.ItemTemplate="{StaticResource ImageDataTemplate}"
    ...    

如您所见,我还添加了“ItemTemplate”支持...

* 命名问题思考 #1

我对“ListBox”这个名称有异议,因为“Box”一词暗示了视觉品质,这与 WPF 关于控件“无外观”的核心概念之一相矛盾。这个控件更好的名称是“SelectableItemsControl”吗?

跨域问题

正如本文名称所暗示的,应用程序的核心功能是利用谷歌图片搜索 API,以传送带形式显示来自**不同来源(域)**的图片。
Silverlight 中禁止跨域数据,**除非**

  • 数据是从已批准的 WCF 服务获取的(服务具有有效的用户访问策略文件)
  • Silverlight 应用程序作为 Out-Of-Browser (OOB) 应用程序运行。因此,它不受 Silverlight 的跨域限制。

在此示例中,我尝试结合两种选项
启动时,应用程序(Web 浏览器中的普通 Silverlight)检查本地 WCF 是否存在。这个自托管的基于控制台的服务充当 Silverlight 和实际数据源之间的代理。就像任何其他 EXE 一样,它拥有跨域数据访问权限,并且由于它发布运行时生成的 User-Access-policy 文件,因此它是 Silverlight 的有效数据源。
我必须承认我喜欢这种 WCF(自托管基于控制台的),我将其视为老式手写、自包含、基于套接字的 TCP 服务器与传统依赖 IIS 的 WCF 之间的“缺失环节”。
很高兴发现已经有人编写了这样的服务 - 为自托管 TCP 服务上的 SL 应用程序启用跨域调用,所以我需要做的就是添加 GetImageBytes/s 方法。
注意一点 - 这个服务实际上会“动态”创建一个“_clientaccesspolicy.xml_”文件,并将其返回给端口 80 上的调用(WCF 客户端在此端口检查此文件),这意味着 IIS 不应该运行!!!因为它使用相同的端口(默认 HTTP 端口)。

回到这个示例,正如我之前所说,在启动时,应用程序检查本地 WCF 是否可用(通过对服务进行“Timeouted”调用)。
如果可用 - 应用程序将此服务用作跨域代理 - 应用程序使用 URL/s 参数调用服务上的 GetImageBytes/s,该服务将下载字节数组并将其返回给应用程序。
如果不可用(对服务的回音调用超时)- 应用程序会提示用户两个选项

  • 将此 Silverlight 应用程序安装到本地机器并作为具有跨域权限的 OOB 运行。
  • 下载并运行本地 WCF,然后刷新当前 Silverlight 的网页。

Using the Code

面板

然后,当你开始迭代 2(这是构建迭代的开始)时,你可能想要复制测试用例并将它们重新分类到迭代 2。这还允许对测试用例进行粒度跟踪,并允许你说某个测试用例在一个迭代中是准备好的,但在另一个迭代中不是。同样,如何做到这一点取决于你以及你希望如何报告。 “场景”部分提供了更多细节。

我使用高中几何定义了适合面板区域的传送带路径。
我通过定义两个连接外弧的圆,然后将整个图形倾斜到所需角度来完成,像这样

接下来,以非线性方式定义沿此路径的位置(我希望物品在“更近”(更大)时更分散,这样物品信息对用户更相关时,就不会被其他物品遮挡太多)。

* 由于这部分(“构建”)在我的优先级列表中不是很高,所以这段代码远非完美...

动画

出于性能原因,所有定位/大小调整都使用 RenderTransformTranslateScale 完成,从而省略了许多耗时的面板及其子项的测量/排列过程。
所有子项最初都放置在 (0,0) 位置,然后通过更改其 RenderTransform 的值进行定位/移动(动画)。

我测试了各种传送带转动的方法,发现“位置到位置”动画方法提供了最佳性能。

需要注意的一点是 Zorder 方面
尽管每个面板都有一个 Arrange 覆盖方法,但实际决定面板子级 zorder 的是它们在面板内部 Children 集合* 中设置的实际顺序。

* 命名问题思考 #2

我发现“Arrange”这个术语令人困惑,因为它与子项的 Z 排序无关。更好的名称是“Positioning2D”吗?

问题在于:项目沿传送带放置的顺序与它们在面板子级集合中存储的顺序不同。
当传送带转动时,应同时考虑“两种”顺序。

以下插图可能会澄清这一点

蓝色='自然顺序'
红色=ZOrder

因此,当将子级从一个位置动画到下一个(或上一个...)时

  • 下一个位置并不总是子集合顺序上的相邻位置。
  • 应该发生一些额外的位置切换。

总而言之,转动传送带(单步)需要两个步骤

  • 重新排列子集合,使 zorder 与新指定的职位排列匹配。
  • 通过渲染变换值将元素动画到指定位置和大小。

动画管理

使用“位置到位置”动画方法意味着传送带的每一次转动(单步)都需要发生多个动画(故事板 - 每个子项一个)。
有几个因素需要处理

  • 等待当前转弯步骤中的所有动画结束,然后再开始下一个单步动画。
  • 加速 - 用户“请求”在**同一方向**上转动传送带,同时仍在转动。
    - 这还有一个额外的含义 - 不仅下一个“单步动画”应该更快,而且当前运行的动画也应该“更新”它们的速度,这样用户将立即收到其加速请求的反馈!
  • 停止 - 用户在仍在转动时“请求”以**相反方向**转动传送带。
  • 减速 - 没有用户“请求”转动传送带。
  • 转动停止 - 随着转动减速,当低于预定义的某个速度时 - 转动应该停止。

我不会深入探讨每个因素的实际代码机制,而是将指出它们都涉及的子程序

public void DoSpinAnimation(int dir)
...
private void UpdateNewDuration(Storyboard sb, TimeSpan ts)
...
private async void DoChildrenSingleStepAnim(int dir)
...
private void AnimateChildrenToPos(int dir)
...
void SingleElementAnimation_Completed(object sender, EventArgs e)

“小伎俩”

  • 谁在最上面的问题...
    如右上角的图像(蓝色数字)所示 - 元素 #8 在元素 #9 的上方,但当传送带顺时针转动时,元素 #9 应该在元素 #8 的上方。由于 zorder 是首先更新的(在动画之前),将发生不必要的翻转(逆时针方向也是如此)。
    为了解决这个问题,这两个“有问题”的元素被分开,这样它们就不会重叠。

        * 同样的问题也出现在元素 #1 和 #16 上 - 在那里,我没有“玩”这个把戏,因为它几乎不明显(而且我很懒...)

  • 当元素沿弯曲路径从一个位置动画到另一个位置时,它们实际上是直线移动的,虽然位置相对接近,但几乎不明显,但是...
    作为前一个“小伎俩”的结果,#8 和 #9 元素应该沿着长长的弯曲距离移动到下一个位置。
    一种解决方案是将这种特殊情况动画分解为沿曲线的多个小分位置(关键帧)。
    另一种解决方案是使用单个**缓动关键帧**为这种特殊动画提供弯曲路径,并通过操纵缓动函数对抗缓动模式属性来实现弯曲路径。
    ...
    SineEase seX = new SineEase(); seX.EasingMode = dir < 0 ? 
        EasingMode.EaseOut : EasingMode.EaseIn; edkfX.EasingFunction = seX;
    ...
    ...
    SineEase seY = new SineEase(); seY.EasingMode = dir < 0 ? 
        EasingMode.EaseIn : EasingMode.EaseOut; edkfY.EasingFunction = seY;
    ...

GoogleSearchConvBeltUserControl

顾名思义,此用户控件用于搜索(使用 Google 图片搜索 API)和显示(使用传送带面板)图片。

实际工作在其 ViewModel 中完成
SearchCommand 上,从 Google 搜索 API 获取 ImageResults 列表。

GimageSearchClient client = new GimageSearchClient(); 
Task<IList<IImageResult>> t = Task<IList<IImageResult>>.Factory.FromAsync
(client.BeginSearch, client.EndSearch, _SearchText, MaxNumOfResultsSelected, null); 
await t;  

然后根据应用程序的配置(参见“**跨域问题**”部分),生成图像对象。

if (IsImageBytesByUrlServiceAlive)
{
...
    // set Image-Source to the Bytes received from service    
...
}
else
{
    // when running as OOB : Image-Source can be URL string, so, 
    // no additional work is needed. 
    Images = new ObservableCollection<MyImageRes>
        (t.Result.Select(ir => new MyImageRes(this, (IImageResult)ir)));
}

面板的 Items-Source 绑定到这些图像(参见“**可控可绑定面板**”部分),因此它们以传送带的形式显示和动画。
此外,ViewModel 会更新其 ViewFetch/Search 状态,以便视图可以设置适当的 **Visual-State**。

UserControl 还将来自传送带面板的“左键单击图像”信息**向上**传递到应用程序主窗口,以获得窗口范围的响应。

MainPage

包含四个平铺(有重叠)的传送带图片搜索 UserControl
如前所述,如果在浏览器中运行 - 检查代理服务可用性,并相应地重新排列。
从任何 UserControl 接收到 ShowLargeImage 事件时 - 动画图像以显示其大尺寸、高分辨率版本。

测试

构建解决方案(_Release_)
运行 - _...\GoogleSearchConvBeltPanel\DemoProject\Bin\Release\DemoProjectTestPage.html_。
系统将提示您显示与“跨域”相关的消息...
运行 ImageBytesByUrl 服务(位于“_GoogleImageSearchService_”文件夹中)
或者,右键单击并将应用程序安装为 OOB,然后从开始菜单或桌面快捷方式运行它。

© . All rights reserved.