在前面一篇“新年快乐”的随笔中,咱们介绍了WinRT中的简单动画实现。其实在使用Windows/Windows Phone时,咱们都会看到一些动画,最简单的好比按下一个button时,该button的状态变化就是动画的一种。再好比弹出式窗口或菜单,也是一种动画。WinRT中的动画种类不少,可是分类有点儿让初学者摸不着头脑:主题过渡,主题动画,视觉转换,情节提要动画。这些咱们就不说了,这里主要说说自定义动画,或者说是情节提要动画(Storyboard Animation),由于这种动画是咱们要经常使用的。git
可是在一个非游戏类的App中添加动画是有原则的:在UI状态之间进行快速流畅的过渡,但不会使用户分心;超出用户的预期,可是又不会让用户厌烦。固然最大的前提是你的App的基本功能比较完美。若是有两个App实现了相同的功能,一个有动画,一个没有,你会喜欢哪一个呢?答案显而易见。何况在WinRT中,动画实现比较简单,效果又很好,因此just do it!程序员
今天咱们按实现方式介绍三类动画:单一动画,复合动画,关键帧动画。其中还分别介绍了用XAML/Code如何实现动画。github
在这个页中,点击三个蓝色的收藏类别条(分类/博主/博文),都会触发两个动画:windows
1)类别条自己作360度的X轴旋转app
2)对应的类别条下方的ListView作FadeIn/FadeOut的显示/隐藏过渡ide
先说360度旋转的作法。咱们定义一个Template Control,而后在该Control的Style中定义动画:函数
<Style TargetType="local:FavoriteGroupControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:FavoriteGroupControl"> <Grid x:Name="grid_Header" Height="60" Background="{ThemeResource CNBlogsThemeColor}"> <Grid.Projection> <PlaneProjection/> </Grid.Projection> <Grid.Resources> <Storyboard x:Name="sb_Roll"> <DoubleAnimation Storyboard.TargetName="grid_Header" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" From="0" To="360" Duration="0:0:00.50"/> </Storyboard> </Grid.Resources>
…… </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
我去掉了不重要的部分,只留下了要说明的部分,完整代码请看Windows Phone project中的Theme/Generic.xaml。性能
首先要定义<Grid.Projection>属性,<PlaneProjection/>表示该Grid须要作X/Y/Z轴的旋转,这个定义是必须的(若是不定义的话后面会出错)。其次,要在<Grid.Resources>中定义Storyboard,它包含有一个<DoubleAnimation>(在后面的动画中会在一个Storyboard中包含多个DoubleAnimation)。动画
再看DoubleAnimation的细节:this
1) Storyboard.TargetName指明咱们要对名字叫作gird_Header的控件下毒手
2)Storyboard.TargetPrpoerty指明了咱们要玩弄那个控件的PlaneProjection.RotationX属性
3)From/To指明了要把该控件旋转一周即360度
4)Duraion指明在0.5秒内完成
好了,动画定义好了,如何触发呢?在MainPage.xaml中,你能够找到如下代码段:
<local:FavoriteGroupControl x:Name="fgc_Category" Tapped="sp_category_Tapped" Margin="0,10"/>
这里定义了一个sp_category_Tapped事件,顺藤摸瓜,咱们在MainPage.xaml.cs中找到如下代码:
private void sp_category_Tapped(object sender, TappedRoutedEventArgs e) { this.fg_Category.Tapped(); }
请注意!一个控件的内置动画只应该在其内部触发,而不是由外部控制。因此,此次摸的瓜是个傻瓜:) 真正的触发动画的Code应该在FavoriteGroupControl.cs中找:
protected override void OnTapped(TappedRoutedEventArgs e)
{
Storyboard sb = this.GetTemplateChild("sb_Roll") as Storyboard;
if (sb != null)
{
sb.Begin();
}
}
它先根据名称“sb_Roll”得到Storyboard的实例sb,而后调用其Begin()方法使其开始旋转。在XAML中定义的Storyboard,都要经过事件处理代码调用Begin()来激活动画。
这里有两点要说明:
1)为何用动画?由于凡是在用户点击屏幕时,咱们都应该给予视觉上的响应,省得心急的用户狂点屏幕形成手指受伤,做为程序员的咱们要有爱心
2)为何用旋转动画?由于我喜欢,就让我任性一次吧,不容易啊。固然也可用别的动画,好比斜一下,或者陷下一点儿。
3)为何在控件内部调用Begin()?由于你给人家提供一个控件,按下后旋转是该控件的预约行为,不要再让使用该控件的人再去管什么动画操做。固然,你也能够提供一个TemplateBinding属性来让使用该控件的人指定是否须要动画,而后在控件内部根据设置调用或不调用动画。
该部分第二个动画是显示或隐藏ListView,此次咱们用另一种方法实现的动画,用Code实现,而不是用XAML实现。看code:
class FavoriteGroup { bool ShowListView = true; ListView lvDetail; Storyboard sbShow, sbHide; public FavoriteGroup(ListView lv) { this.lvDetail = lv; CreateStoryboard(); this.sbHide.Completed += sbHide_Completed; } private void sbHide_Completed(object sender, object e) { this.lvDetail.Visibility = Windows.UI.Xaml.Visibility.Collapsed; } public void Tapped() { this.ShowListView = !this.ShowListView; if (this.ShowListView) { this.lvDetail.Opacity = 0; this.lvDetail.Visibility = Windows.UI.Xaml.Visibility.Visible; this.sbShow.Begin(); } else { this.sbHide.Begin(); } } private void CreateStoryboard() { // show listview in 1 second DoubleAnimation daShow = new DoubleAnimation(); daShow.From = 0; daShow.To = 1; daShow.Duration = new Windows.UI.Xaml.Duration(TimeSpan.FromSeconds(1)); this.sbShow = new Storyboard(); sbShow.Children.Add(daShow); Storyboard.SetTarget(daShow, this.lvDetail); Storyboard.SetTargetProperty(daShow, "Opacity"); // hide listview in 1 second DoubleAnimation daHide = new DoubleAnimation(); daHide.From = 1; daHide.To = 0; daHide.Duration = new Windows.UI.Xaml.Duration(TimeSpan.FromSeconds(1)); this.sbHide = new Storyboard(); sbHide.Children.Add(daHide); Storyboard.SetTarget(daHide, this.lvDetail); Storyboard.SetTargetProperty(daHide, "Opacity"); } }
在构造函数中,调用了CreateStoryboard()方法,首先定义了两个Storyboard,在每一个Storyboard中定义了一个DoubleAnimation,一个是用1秒时间把ListView的Opacity值从0变到1(显示),另外一个是用1秒时间把Opacity从1变到0(隐藏)。上面的写法等价于这个XAML:
<Storyboard x:Name="sbShow"> <DoubleAnimation Storyboard.TargetName="lvDetail" Storyboard.TargetProperty="Opacity" From="0" To="1" Duraion="0:0:1"/> </Storyboard> <Storyboard x:Name="sbHide"> <DoubleAnimation Storyboard.TargetName="lvDetail" Storyboard.TargetProperty="Opacity" From="1" To="0" Duraion="0:0:1"/> </Storyboard>
为何在这里不用XAML写法而用Code直接定义呢?是为了显示技巧吗?你猜对啦!由于在MainPage.xaml中,有三个ListView,分别为lv_category, lv_author, lv_blog,若是要用XAML定义动画,要对这个三个ListView各写一遍,重复了三次,只是ListView的名字不一样,太难看啦!注意素质!因而搞了一个FavoriteGroup类(可能名字不太好,叫刺杀金xx怎么样?),里面用code包了一下,把ListView做为参数传入,就能够复用code啦。哎,纯属刁民小技,让各位看官见笑了。
咱们再看看稍微复杂些的动画:在一个Storyboard中包含多个DoubleAnimatoin。
<Storyboard x:Name="sb_LogoMoveUp"> <DoubleAnimation Duration="0:0:0.8" From="200" Storyboard.TargetName="image_Logo" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.GlobalOffsetY)" To="0" /> <DoubleAnimation Duration="0:0:0.8" From="360" Storyboard.TargetName="image_Logo" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationZ)" To="0" /> <DoubleAnimation Duration="0:0:0.8" From="0" Storyboard.TargetName="image_Logo" Storyboard.TargetProperty="Opacity" To="1" /> </Storyboard>
在SettingsPage.xaml中,咱们在sb_LogoMoveUp的Storyboard中定义了三个动画:
1)把image_Logo上移200个像素
2)让image_Logo旋转360度
3)让image_Logo透明度从0变成1
以上三个动画同时进行,都是在0.8秒内完成,因而咱们看到了那个图片从下方“滚动”(不是滑动)到上方,并逐渐清晰,整个过程非常优雅大方,毕竟滚动摩擦比滑动摩擦小不少(扯远了),不拖泥带水,颇有节操的。
要说明几点:
1)用复合动画,能够对一个控件的不一样属性进行同时操做,以造成单一动画没法完成的复杂效果。好比咱们是对image_Logo的三个属性同时进行操做。固然也能够不一样时,用BeginTime属性来设置一下启动时间便可。
2)在这里为何要用动画?由于我喜欢超出用户的预期,给他们以动态视觉享受,而不是干巴巴的看着一个图片发呆。用户一高兴,没准儿就给个好评了。
你们能够查看Windows 8.1 project的Theme/Generic.xaml看完整代码。
在这个Control中,左边那个图,点击右侧箭头,将会向左滑动,成为右边那个样子。
这个滑动过程不是线性的,所以要用到关键帧,意思是说:在某个时间点,作这件事;到下一个时间点,再作那件事。看下面的XAML代码:
<Storyboard x:Name="sb_Button_out"> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="SecondViewTrans"
Storyboard.TargetProperty="X" BeginTime="0:0:0">
<SplineDoubleKeyFrame KeyTime="00:00:00.00" Value="480"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.10" Value="460"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.20" Value="400"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.30" Value="300"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.40" Value="170"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.50" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.54" Value="32"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.58" Value="60"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.62" Value="80"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.66" Value="92"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.70" Value="96"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.74" Value="92"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.78" Value="80"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.82" Value="60"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.86" Value="32"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.90" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
其中的那个<SplineDoubleKeyFram>就是关键帧的定义,在每一个时间点,都定义了目标控件的X位置。能够看到第6个关键帧,X值已是0了,为何又从0变大了呢?这样就产生了触底反弹的效果,让目标控件弹回到最大96的位置,最后再回到0。
须要注意的是,关键帧只能对某个控件的惟一的一个属性操做,不能同时操做多个属性。而在上一节的复合动画中,是对某个控件的多个属性同时操做,可是不能对某个属性定义两次DoubleAnimation。这个要牢记。
哦,办公室已经自动关灯了,看样子该给公家省电了,拍屁股回家吧。可是你们要记住哟,动画不能乱用,不能让用户讨厌,不能人为影响系统流畅度,不能影响系统性能。
好比在博客园UAP的WP版本中,咱们在不少小地方使用了动画,好比热门页中下拉ListView时右上角的数字变化,博主页中下拉ListView时页面标题的变化,等等。这些动画都是和当前的操做密切相关的,但它们又不会强烈吸引用户注意。
在“新年快乐”页中,是故意要展现一下一些东西,因此作了不少动画。另外,在“新年快乐”页中,还用到了不使用Storyboard/DoubleAnimation/KeyFrame等技术,而是用纯code操做XAML元素的位置来制做的动画(游戏开发的基本功),咱们后面再聊!
分享代码,改变世界!
Windows Phone Store App link:
http://www.windowsphone.com/zh-cn/store/app/博客园-uap/500f08f0-5be8-4723-aff9-a397beee52fc
Windows Store App link:
http://apps.microsoft.com/windows/zh-cn/app/c76b99a0-9abd-4a4e-86f0-b29bfcc51059
GitHub open source link:
https://github.com/MS-UAP/cnblogs-UAP
MSDN Sample Code:
https://code.msdn.microsoft.com/CNBlogs-Client-Universal-477943ab
MS-UAP
2015/1/9