前言
在前面一篇“新年快樂”的隨筆中,我們介紹了WinRT中的簡單動畫實現。其實在使用Windows/Windows Phone時,我們都會看到一些動畫,最簡單的比如按下一個button時,該button的狀態變化就是動畫的一種。再比如彈出式窗口或菜單,也是一種動畫。WinRT中的動畫種類很多,但是分類有點兒讓初學者摸不着頭腦:主題過渡,主題動畫,視覺轉換,情節提要動畫。這些我們就不說了,這里主要說說自定義動畫,或者說是情節提要動畫(Storyboard Animation),因為這種動畫是我們要常用的。
但是在一個非游戲類的App中添加動畫是有原則的:在UI狀態之間進行快速流暢的過渡,但不會使用戶分心;超出用戶的預期,但是又不會讓用戶厭煩。當然最大的前提是你的App的基本功能比較完美。如果有兩個App實現了相同的功能,一個有動畫,一個沒有,你會喜歡哪個呢?答案顯而易見。況且在WinRT中,動畫實現比較簡單,效果又很好,所以just do it!
今天我們按實現方式介紹三類動畫:單一動畫,復合動畫,關鍵幀動畫。其中還分別介紹了用XAML/Code如何實現動畫。
收藏頁面中的動畫 - 單一動畫
在這個頁中,點擊三個藍色的收藏類別條(分類/博主/博文),都會觸發兩個動畫:
1)類別條本身做360度的X軸旋轉
2)對應的類別條下方的ListView做FadeIn/FadeOut的顯示/隱藏過渡
用XAML定義動畫
先說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的細節:
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屬性來讓使用該控件的人指定是否需要動畫,然后在控件內部根據設置調用或不調用動畫。
用Code定義動畫
該部分第二個動畫是顯示或隱藏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啦。哎,純屬刁民小技,讓各位看官見笑了。
Setting頁面中About的動畫 - 復合動畫
我們再看看稍微復雜些的動畫:在一個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版本中的PostControl動畫 - 關鍵幀動畫
大家可以查看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