随笔-193  评论-1122  文章-1  trackbacks-4

《101 Windows Phone 7 Apps》读书笔记-Silly Eye

课程内容

Ø Animation

Ø Event Triggers

Ø Named Resources

Ø Settings Page

Ø Color Picker

Ø Clipping

 

    Silly Eye是一个非常吸引眼球的应用,特别是在一群小孩子中间。该应用程序会以一种有趣、近乎疯狂的方式来展示一个巨大的卡通眼球。展示该应用程序,只需要将手机放在右眼的前面,并假装它就是你的右眼球。本应用程序介绍了一些有用的新技术,并且与创建新的页面和选择用户自定义的颜色相关。但最重要的是,这是与本书第二部分的主题“Transforms & Animations”中“Animations”相关的第一个应用程序。

 

Introducing Animation

    大多数人看到动画,就会认为是一种类似于卡通的东西,它利用连续快速地播放图片来模拟运动效果。在Silverlight中,动画有一个更加详细的定义:在时间轴上改变一个属性的值。这与运动相关,例如,通过增加元素的宽度来营造一种生长的效果,或者,改变一个元素的不透明度来营造另一种完全不同的效果。

    在时间线上改变一个属性的值有很多种方法。最经典的方法是使用定时器,比如,很多前面章节中使用的DispatcherTimer,以及基于定时器触发时间而设置的周期性回调方法(Tick事件处理)。在这种方法中,我们可以手动更新目标属性(即基于逝去的时间,通过数学计算来决定当前的属性值),直到最终的设定值。这个时候,我们可以停止计时器,并且/或者删除事件处理程序。

    但是,Silverlight提供了一种更容易使用的动画机制,它更加强大,性能要比定时器方法更加出色。这是一种以Storyboard的对象为中心的机制。Storyboard包含了一个或多个特定的动画对象,它们被应用到特定元素的指定属性中。

    Silly Eye使用了三个Storyboard来实现动画。为了理解Storyboard的概念和工作原理,我们来学习以下三个内容:

➔ 与瞳孔相关的 Storyboard

➔ 与虹膜相关的 Storyboard

➔ 与眼睑相关的 Storyboard

 

The Pupil Storyboard

     下面是Silly Eye应用程序中,将Storyboard应用到瞳孔中,使其进行重复地扩张和缩小。其注意点如下:

➔ Storyboard.TargetName这个可附加的属性表明,该动画被应用到本页面中一个名为Pupil的元素中,而Pupil是一个椭圆。

➔ Storyboard.TargetProperty这个可附加的属性表明,Pupil的StrokeThickness属性被设置了动画效果。

➔ Storyboard中的DoubleAnimation表明,StrokeThickness这个值会在半秒钟之内,从100变为70。DoubleAnimation中的“Double”代表了目标属性的类型(因为StrokeThickness类型是double)。

➔ 因为AutoReverse被设置为true,所以,在StrokeThickness达到70时,会自动从70变为100。由于RepeatBehavior被设置为Forever,所以只要应用程序启动以后,这种重复的动作就会一直持续。

➔ EasingFunction属性(设置为ElasticEase的实例)控制着StrokeThickness值是如何在时间线上进行改写的。这部分的内容会在下面的“Interpolation”节介绍。

开始动画中,storyboard的Begin方法调用如下:

this.PupilStoryboard.Begin();

    这个动画在整个应用程序中表现出来的效果如图12.2所示。Pupil这个椭圆的元素在背后代码中被赋为亮蓝色的画刷。

image

图12.2 PupilStoryboard使得Pupil的stroke(蓝色部分)厚度从100缩小到70,因而使得黑色填充变大。

    在XAML中,有一种方法来触发storyboard的所有行为。因此,我们没有必要在背后代码中调用它的Begin方法。我们可以为一个元素的Triggers属性添加一个事件触发程序。幸亏这个特殊的BeginStoryboard元素,在grid的Loaded事件中,它会内部调用storyboard 的Begin方法。而Loaded事件是Silverlight事件触发中支持的唯一一个事件。

 

Types of Animations

    Silverlight 提供了animation类来实现四种不同的数据类型:double、Color、Point和Object。在Silly Eye应用程序中,只使用了double属性。剩余的类型在第15章“Mood Ring”中介绍。如果我们想要在时间线上改变元素中double类型的属性值(比如Width、Height、Opacity、Canvas.Left等等),我们可以使用DoubleAnimation的实例。如果我们想要在时间线上改变元素中Point类型的属性值(比如一个线性渐变brush的StartPoint和EndPoint属性),我们可以使用PointAnimation的实例。目前为止,由于大量的double类型的属性需要动画效果,所以DoubleAnimation是最常用的动画类。

并不是所有的属性都可以做动画效果,即使它的数据类型与动画类使用的数据类型匹配!

本章内容所讨论的动画机制,只有那些被称为“依赖项属性”(dependency property)的类型才能够使用。幸运的是,大部分视觉元素中的属性就是该类型的。依赖项属性的有关内容会在第18章“Cocktails”中讨论。与判断一个事件是否是“路由事件”(routed event)类似,我们可以通过检查类中所包含的一个名为PropertyNameProperty的DependencyProperty类型的静态字段来决定该属性是否是依赖项属性。如果该类中包含了这种字段,如ellipse类中的StrokeThicknessProperty字段,那么它就是一个依赖项属性。

    为了达到理想的效果,思考一个元素的哪些属性来做动画,这需要通过一些试验来决定。例如,如果我们想要一个元素以渐变的方式出现,那么,对它的Visibility属性做动画就没有任何意义,因为在Collapsed 和 Visible两者之间没有中间值。相反,我们应该对它的Opacity属性做动画,它的值是double类型的,范围从0到1。

 

Interpolation

    认识下面这点很重要:在默认情况下,DoubleAnimation通过时间线上的线性插值来达到平缓地改变double类型的属性值。换句话说,在一秒钟之内,如果该值从50增加到100,那么,在0.1秒的时刻,它的值就是55(时间增加10%,其值也线性增加10%),在0.5秒的时刻,它的值就是75(时间增加50%,其值也线性增加50%),以此类推。这也就是图12.2中,StrokeThickness属性的中间值是85的原因。

    但是,Windows Phone应用程序中使用的大部分动画是非线性的。而且,他们更倾向以突然加速或者突然减速的方式从一个值改变到另一个值。这种方式使得动画变得妙趣横生。我们可以通过应用一个缓和的函数来实现这种非线性的动画效果。

    这种过渡函数负责属性值从起始到最终值之间的自定义插值。Pupil Storyboard使用了名为ElasticEase的函数来实现这种行为。图12.3展示了属性值从100减小到70时,使用默认的线性变换和弹性变换之间的差异。在这种情况下,85这个中间值并不是在中间时间点达到,它其实更接近于终点时才达到。

image

图12.3 ElasticEase过渡函数极大地改变了double类型值从100减小到70的方式。

    Silverlight提供了11个不同的过渡函数,每个函数有三种不同的模式,有些函数提供了更深层次的属性行为自定义。例如,ElasticEase具有Oscillations 和 Springiness 属性,默认设置为3。在实际应用中,如果我们想要在动画中加入自定义的函数,那么这种自定义行为的可能性是无穷无尽的。与默认的线性过渡相比,本应用中使用的过渡函数为用户提供了一种完全不同的体验。

    附录D“Animation Easing Reference”中展示了每个内置过渡函数的行为。我觉得这些函数非常有用,因为每次当我想要设计一个新的动画时,我都会回去参考这些函数。

另一种产生非线性动画的方法

过渡函数并非是产生非线性过渡动画效果的唯一方法。第14章“Love Meter”中讨论的Keyframe animations,使得我们可以利用不同的差值方法,将一个动画分解为多个片段。

 

The Iris Storyboard

    Silly Eye应用程序将以下Storyboard应用到了一个名为Iris的canvas控件中,使得眼球看上去在左右移动。其注意点如下:

➔ TargetProperty的语法与其名称相比,稍显复杂。当它设置为一个可附加的属性(如Canvas.Left)时,它必须被包含在括号内。

➔ 该动画使用了一个不同的过渡函数,使得其运动的边界更加明显。关于BounceEase的行为,请参考附录D。

➔ 该动画缺少了From值!这是可行的,而且我们推荐这样处理。当From值没有指定时,动画就从目标属性的当前值开始,而不管该值大小为多少。同样,一个动画可以指定From值,但并不指定To值!这样的话,动画就从属性的指定值开始,到当前值为止。

    为了达到平缓的效果,将From设置为缺省是很重要的,特别是动画开始于一个可重复的用户输入。比如,在用户点击一个界面元素时,开始它增长的动画,那么,每次快速的点击会使得其大小迅速变回From设定的值。但是,通过缺省的From值设定,随后的点击也会使动画从当前值开始,使得动画更加的平滑和自然。

在目标属性值无法插值的情况下,我们必须指定From 和 To 的值!

假如我们尝试着为一个auto-sized元素的宽度或者高度做动画效果,而它的From和To没有指定,那么,动画效果就不会出现。当元素的宽度或者高度被设置为Double.NaN(非数值)时,它的大小是自适应的。因为当两个值中存在一个非数值的数时,DoubleAnimation也就无法完成插值的操作。而且,将动画应用到ActualWidth或者ActualHeight中去(它们被设置为真实的宽度或高度值,而非NaN),这并不是一个好的选择。因为这些属性是只读的,而且并不是依赖项属性。相反,为了有动画效果,我们必须显式地设置目标元素的宽度/高度。

    对于Pupil Storyboard来说,我们必须调用Storyboard的Begin方法来使得它开始工作。

    动画的效果如图12.4所示。Iris Canvas 除了包含 Pupil ellipse(事实上,它的外围就是Iris)以外,还包括另外两个Ellipse,它们为Iris增添了“光泽”。因为Canvas控件的位置加入了动画效果,所以其包含的所有视觉元素都会一起移动。

image

图12.4 IrisStoryboard水平移动Iris Canvas,其值从287(它初始的Canvas.Left值)增加到543。

    动画效果类中,也有一个名为 By 的字段,它可以用来代替 To 字段。下面的动画意味着“在当前属性值的基础上,增加256”:

<DoubleAnimation By=”256” Duration=”0:0:2”/>

    对于缩减当前值时,出现负值的情况也是支持的。

 

The Eyelid Animation

    Silly Eye应用程序中使用的最后一个storyboard,用来对具有皮肤颜色的Eyelid Ellipse模仿眨眼的效果。对于Pupil Ellipse来说,皮肤颜色的画刷在背后的代码中进行设置。其注意点如下:

➔ Storyboard.TargetName 和 Storyboard.TargetProperty 这两个属性被设置为attachable的原因是:它们可以在单独的动画中使用,而不用去理会任何Storyboard中的设置。该Storyboard以 Eyelid Ellipse 中的 Height 和 Canvas.Top 这两个属性为目标。因此,单个目标的名字被标记在了Storyboard上,但是多个不同的目标属性被用来标记多个不同的动画效果。

➔ Canvas.Top 与 Height 具有同步的动画效果,所以椭圆在垂直缩小的同时,保持在中间的位置。下一章介绍的“Transforms”,提供了一种更加快捷的方法来实现这个效果。

➔ 这两种动画都使用了默认的线性插值方法。它们的移动速度如此之快,以至于没有必要再去尝试别的更具生命力的方法。

➔ Storyboard不仅仅是一个简单的用来给相关的物体实现动画效果的容器。该Storyboard具有自身的持续行为和重复行为!这两种动画只持续了0.2秒钟的时间(0.1秒之内将属性的当前值从380减小到50,另外的0.1秒钟之内,由于其auto-reverse设置,将属性值变回到原来的值)。但是,因为给Storyboard的持续时间是3秒钟,而且它自身具有auto-reverse设置(并非是它的子元素),动画在后面的时间里面保持不动,知道最后3秒时间消耗完。然后,持续0.2秒钟的动作再次开始,之后便是2.8秒钟的静止时间。因此,该Storyboard使得眼睛眨得非常快,且在3秒钟发生一次。

    这种动画的效果如图12.5所示(在C#中调用 Begin 方法开始动画)。因为Eyelid Ellipse与背景的颜色一致(有意在其左边的相邻区域用黑色做填充),我们就无法看到Eyelid Ellipse本身。相反,一旦Ellipse的高度(380)小于其填充厚度的两倍(400),我们可以看到其内部的空间急剧缩小到0。

image

图12.5 Eyelid Storyboard 压缩 Eyelid Ellipse的高度,并向下移动,使其保持居中。

 

Storyboard and Animation Properties

    我们已经了解了 Duration、AutoReverse 和 RepeatBehavior 属性,它们可以应用到单独的动画或者一个完整的Storyboard中去。总的来说,有6种属性可以应用到 Storyboard 和Animation 中去:

➔ Duration:Animation 或者 Storyboard 的长度,默认值为1秒。

要谨慎指定duration的值!

一个 duration 的字符串格式与 TimeSpan.Parse 这个接口函数的格式相同:

                                                  days.hours:minutes:seconds.fraction

这里允许使用快捷方式,所以我们不用指定每个字符串。但是,它的行为可能不是如你所愿。字符“2”表示2天,而非2秒。字符串“2.5”表示2天零5个小时!字符串“0:2”表示2分钟。如果大多数动画都不能超过几秒钟的时限,那么,典型的使用格式就是 hours:minutes:seconds 或者 hours:minutes:seconds.fraction。所以,2秒可以表示为“0:0:2”,半秒可以表示为“0:0:0.5”或者“0:0:.5”。

➔ BeginTime:动画或者Storyboard延时开始的时间长度,由一个具体的时间值表示,默认为0。对于子元素的动画,Storyboard可以使用自定义的BeginTime值,使得它们可以相继开始动画,而非同时开始。

➔ SpeedRatio:持续时间(Duration)的乘子,默认为1。我们可以将它设置为任意的大于0的double类型的值。一个小于1的值能够减缓动画的速度,一个大于1的值能够加速动画的速度。SpeedRatio不会影响BeginTime。

➔ AutoReverse:该属性设置为True时,使得动画或者Storyboard达到终点以后,实现自动回播。回播花费同样长度的时间,所以SpeedRatio也会影响回播。需要注意的是,通过BeginTime指定的延时并不会影响回播。回播一般会在正常的动画结束以后,立即启动。

➔ RepeatBehavior:可以设置为一个时间段,或者设置为一个字符串,例如“2x”、“3x”或者“Forever”。因此,我们可以使用RepeatBehavior,使得动画的持续时间减短(或者减少他们的持续时间),或者使得动画自动重复多次(甚至可以是一个带小数的倍数,如2.5倍),或者是永远重复动画(本章就是使用这个方法)。如果AutoReverse设置为True,那么,回播操作也会重复。

➔ FillBehavior:可以设置为 Stop,而它的默认值为 HoldEnd,使得相关的动画完成以后,其属性值恢复到动画之前的值。

 

The Total Length of an Animation

    通过使用诸如BeginTime、SpeedRatio、AutoReverse和RepeatBehavior属性,可以对动画做多方面的调整,在动画开始以后,测试其持续总时间的长度是有难度的。它的Duration值当然不足以描述真正的时间长度!相反,以下的公式描述了一个动画真正的持续时间:

总时间= BeginTime +(Duration *(AutoReverse ? 2:1)* RepeatBehavior)/ SpeedRatio

    这可以在RepeatBehavior属性指定为一个double类型的值时使用(或者使用它的默认值1)。如果RepeatBehavior设置为一个时间段,那么总的时间长度就是RepeatBehavior的值加上BeginTime的值。

 

The Main Page

    Silly Eye主页面的XAML包含了一些矢量图片,一个应用程序栏,以及三个Storyboard。它同样包含了一个“使用说明页面”,暗示用户点击屏幕开始应用,如图12.6所示。因此,我们一开始可以展示应用程序栏,但是应用程序开始运行时,它就隐藏了,因为屏幕上显示的按钮会妨碍应用程序的效果。介绍页面暗示用户他们可以通过点击屏幕,在任何时候达到重新调出应用程序栏的目的。

image

图12.6 应用程序栏只有在“介绍页面”出现使可见

➔ 应用程序栏包含了导向设置页面、说明页面和关于页面的链接。前两个页面会在下面两节中介绍。我们已经在第6章“Baby Sign Language”中学习了关于页面。我们认为,设置页面的链接作为按钮放置在应用程序栏,要好于一个菜单项,因为在本应用程序中,用户对设置进行自定义也是一件很正常的事情(在应用程序的正常操作过程中,应用程序栏不会引入视觉上的混乱,因为它是隐藏的!)。

➔ 注意,三个Storyboard资源的名称被命名为“x:Name”,而不是“x:Key”!这是一种方便的手段,使得我们可以更加方便地使用背后的代码。在我们给资源命名以后,它就可以作为字典中的一个键来使用,或者作为C#生成的一个字段。

➔ 显式的From值已经从Pupil Storyboard的动画中移除了,因为它并不是必须的。这部分内容已经在本章进行了介绍,它有助于理解动画是如何工作的。

➔ IntroTextBlock元素用来监听用户的点击,并且隐藏IntroPanel。它的宽度值为700,比整个页面的宽度还要大,因为如果它距离应用程序栏太近的话,用户在想点击应用程序栏时,可能不小心点到它(然后就会隐藏应用程序栏)。

 

The Code-Behind

➔ 由于XAML中的x:Name标记,通过各自的名称,三个Storyboard在构造函数中初始化。

➔ 页面的Clip属性被设置为一个屏幕大小的矩形区域。这样做是为了在动画页面切换期间,防止对屏幕以外的矢量图形进行渲染。这不仅避免了出现非常奇怪的视觉元素,而且也有助于提高应用程序的性能。所有具有这个Clip属性的UI元素可以被设置为一个任意的几何图形。

 

Geometries Used for Clipping

    Clip属性可以被设置为一些几何形状,这些形状与第5章“Ruler”中介绍的形状对象类似,但又有不同。我们可以使用 RectangleGeometry、 EllipseGeometry 、LineGeometry、PathGeometry 或者是 GeometryGroup 来组合多个几何形状。附录E“Geometry Reference”中将会讨论这些几何形状。

➔ 我们使用两个存储设置来存储皮肤和眼睛颜色的值,它们在OnNavigatedTo事件处理中使用。在OnNavigatedFrom事件处理中,它们没有必要进行储存,因为设置页面会进行处理。这些设置的内容在单独的Settings.cs文件中定义。事实上,眼睛默认的颜色就是手机的主题强调色,就和本章图片中展示的蓝色一样。

➔ IntroPanel的可视性(以及应用程序栏)放置于页面的状态中,所以如果页面在休眠和激活以后,看上去是一致的。无论页面经历了多大的改变,也不要忘记使用页面设置,有了它,在应用程序经历被打断、又重新激活时,我们可以快速并且自动地恢复页面状态。

➔ IntroTextBlock的对齐方式在OnOrientationChanged事件中进行调整,这样做是为了保持它与应用程序栏保持对立的位置关系。前面已经叙述过,在手机的方向为landscape right时,应用程序栏出现在屏幕的左边;当手机的方向为landscape left时,应用程序栏出现在屏幕的右边。

 

The Settings Page

    设置页面如图12.7所示,它使得用户可以为眼睛和皮肤选择不同的颜色。

image

图12.7 设置页面使得用户可以选择Silly Eye应用程序的颜色。

在系统自带的设置程序中,如何为我们的应用程序添加一个设置页面?

在目前Windows Phone 7.0的版本中,我们还无法做到这点。虽然设置应用包含了一个系统设置的列表和一个应用设置的列表,但是后者只是针对系统自带的应用来说的。相反,我们需要为我们的应用程序添加设置页面,使其用户体验和系统自带的设置页面一致。换句话说,对于我们的设置页面,应该使用“SETTINGS”作为应用程序的名称出现在标准的header中,使用应用程序的名称作为页面标题,如图12.7所示。

    对于设置页面的设计指导,请参考第20章“Alarm Clock”中的内容。

    页面设计的注意点如下:

➔ 该页面的自定义header样式从App.xaml文件获得。对于本书中剩余的应用程序来说,App.xaml.cs这个文件同样提供了自定义的页面过渡效果,如第19章“Animation Lab”所述。

➔ 那两个可点击的区域显示了当前的颜色,看上去和按钮很像,但实际上它们只不过是矩形填充。它们的MouseLeftButtonUp事件处理包含了用户对于每种界面颜色改变的处理。

➔ 虽然在不同的方向模式下,内容完全符合屏幕,但是主要的stack panel控件被放置在scroll viewer内。这对于用户来说,很适合触摸操作,因为用户可以用手指拖动屏幕查看内容,并使他们确信浏览了屏幕中所有的内容。

    在很多页面中,例如设置页面、说明页面或者是关于页面,将内容放置于scroll viewer中是一个很好的选择,即使所有的内容可以用一个屏幕来容纳。那样的话,用户可以用手指做一个快速的拖动,从视觉上就可以判定没有更多的内容可以看了(因为scroll viewer控件的scroll-and-squish特性)。这种反馈是非常另用户满意的。当屏幕对于用户的拖动没有反应时,用户会认为他不够用力,可能会再尝试一次。

 

The Code-Behind

    为了使用户能够改变每个颜色,该页面会将用户导航到一个颜色选择页面,如图12.8所示。这个特性页面被本书中的很多应用程序共用,包含在本书的源代码中。它提供了一个标准颜色的调色板,它也允许用户自定义颜色的色相、饱和度和亮度,不管是通过交互式的界面或者是输入一个十六进制的数值(或者是任何能够被XAML解析的字符串,如“red”、“tan”或者是“lemonchiffon”)。调整颜色的不透明度是可选的。

image

图12.8 颜色选择器页面为用户提供了一个漂亮的页面来选择颜色。

    颜色选择器页面通过查询字符串可以接受以下四种参数:

➔ showOpacity-默认值为True,可以将其设置为False来隐藏opacity slider。它也会将调色板顶层的透明颜色移除,并且阻止用户输入透光的颜色。因此,当我们将它设置为False时,我们可以确定一个不透明的颜色将会被选中。

➔ currentColor-当页面呈现时,开始被选择的颜色。它必须作为一个对XAML有效的字符串参数传入。如果指定为一个十六进制的数,“#”必须被移除,这样做是为了与URI混淆。

➔ defaultColor-在颜色选择器页面中,用户点击reset按钮时,可以得到的颜色。它指定的字符串格式要求与currentColor一样。

➔ settingName-隔离存储空间中使用的名称,在从页面返回时,选定的颜色可以从中被找到。在构造一个Setting的实例时,用到了同样的名字。在列表12.4的OnNavigatedTo方法中,当从颜色选择器页面返回时,它自动选择新的颜色数值,那只是因为导航到颜色选择页面之前,需要调用ForceRefresh方法。第20章详细介绍了这种方法。

    使用本书的颜色选择器页面(或者类似的页面)为用户提供一种简便高效的颜色选取方法。在当前的版本中,该页面的主要缺点是只支持portrait的屏幕方向。因此,在landscape屏幕方向下,使用硬件键盘输入一个颜色的十六进制数的用户体验并不好。

 

The Instructions Page

    本应用程序的说明页面如图12.9所示,其注意点如下:

image

图12.9 Silly Eye应用程序中的说明页面

➔ 和设置页面采用的方法一样,主要的内容被放置在scroll viewer中,从而提示用户没有更多的内容可以浏览。

➔ 和主页面中的intro pane一样,单个text block利用LineBreak元素来格式化其文本内容。

➔ 对于背后的代码文件-InstructionsPage.xaml.cs,在其构造函数中,只包含了对InitializeComponent方法的调用。

posted on 2012-08-22 08:07 施炯 阅读(...) 评论(...) 编辑 收藏