博客園客戶端UAP開發隨筆 -- App的心動殺手鐧:動畫


前言

在前面一篇“新年快樂”的隨筆中,我們介紹了WinRT中的簡單動畫實現。其實在使用Windows/Windows Phone時,我們都會看到一些動畫,最簡單的比如按下一個button時,該button的狀態變化就是動畫的一種。再比如彈出式窗口或菜單,也是一種動畫。WinRT中的動畫種類很多,但是分類有點兒讓初學者摸不着頭腦:主題過渡,主題動畫,視覺轉換,情節提要動畫。這些我們就不說了,這里主要說說自定義動畫,或者說是情節提要動畫(Storyboard Animation),因為這種動畫是我們要常用的。

但是在一個非游戲類的App中添加動畫是有原則的:在UI狀態之間進行快速流暢的過渡,但不會使用戶分心;超出用戶的預期,但是又不會讓用戶厭煩。當然最大的前提是你的App的基本功能比較完美。如果有兩個App實現了相同的功能,一個有動畫,一個沒有,你會喜歡哪個呢?答案顯而易見。況且在WinRT中,動畫實現比較簡單,效果又很好,所以just do it!

今天我們按實現方式介紹三類動畫:單一動畫,復合動畫,關鍵幀動畫。其中還分別介紹了用XAML/Code如何實現動畫。

收藏頁面中的動畫 - 單一動畫

image

在這個頁中,點擊三個藍色的收藏類別條(分類/博主/博文),都會觸發兩個動畫:

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的動畫 - 復合動畫

image

我們再看看稍微復雜些的動畫:在一個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中,左邊那個圖,點擊右側箭頭,將會向左滑動,成為右邊那個樣子。

image

這個滑動過程不是線性的,因此要用到關鍵幀,意思是說:在某個時間點,做這件事;到下一個時間點,再做那件事。看下面的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM