|
線性動(dòng)畫的一個(gè)缺點(diǎn)是,它通常讓人覺(jué)得很機(jī)械且不能夠自然。相比而言,高級(jí)的用戶界面具有模擬真實(shí)世界系統(tǒng)的動(dòng)畫效果。例如,可能使用具有觸覺(jué)的下壓按鈕,當(dāng)單擊時(shí)按鈕快速?gòu)椈?,但是?dāng)沒(méi)有進(jìn)行操作時(shí)它們會(huì)慢慢地停下來(lái),創(chuàng)建真正移動(dòng)的錯(cuò)覺(jué)?;蛘?,可能使用類似Windows操作系統(tǒng)的最大化和最小化效果,當(dāng)窗口解決最終尺寸時(shí)窗口擴(kuò)展或收縮的速度會(huì)加速。這些細(xì)節(jié)十分細(xì)微,當(dāng)它們的實(shí)現(xiàn)比較完美時(shí)可能不會(huì)注意到它們。然而,幾乎總會(huì)注意到,粗糙的缺少這些更細(xì)微特征的動(dòng)畫會(huì)給人留下笨拙的印象。 改進(jìn)動(dòng)畫并創(chuàng)建更趨自然的動(dòng)畫的秘訣是改變變化速率。不是創(chuàng)建以固定不變的速率改變的屬性的動(dòng)畫,而是需要設(shè)計(jì)根據(jù)某種方式加速或減速的動(dòng)畫。WPF提供了幾種選擇?;趲膭?dòng)畫和關(guān)鍵幀動(dòng)畫,這兩種技術(shù)都提供了更精細(xì)地控制動(dòng)畫的能力。但實(shí)現(xiàn)更趨自然的動(dòng)畫的最簡(jiǎn)單方法是使用預(yù)置的緩動(dòng)函數(shù)(easing function)。 當(dāng)使用緩動(dòng)函數(shù)時(shí),仍可通過(guò)指定開(kāi)始和結(jié)束屬性值以常規(guī)的方式定義動(dòng)畫。但為了附加這些細(xì)節(jié),需要添加預(yù)先編寫好的修改動(dòng)畫過(guò)場(chǎng)的數(shù)學(xué)函數(shù),使動(dòng)畫在不同的點(diǎn)加速或減速。 一、使用緩動(dòng)函數(shù) 動(dòng)畫緩動(dòng)的最大優(yōu)點(diǎn)是,相對(duì)于其他方法,如基于幀的動(dòng)畫和關(guān)鍵幀動(dòng)畫,這種方法需要的工作少很多。為使用動(dòng)畫緩動(dòng),使用某個(gè)緩動(dòng)函數(shù)類(繼承自EasingFunctionBase的類)的實(shí)例設(shè)置動(dòng)畫對(duì)象的EasingFunction屬性。通常需要設(shè)置緩動(dòng)函數(shù)的幾個(gè)屬性,并且為了得到所希望的效果,可能必須使用不同的設(shè)置,但不需要編寫代碼并且只需很少的XAML。 例如,分析下面給出的兩個(gè)動(dòng)畫,這兩個(gè)動(dòng)畫用于按鈕。當(dāng)用戶將鼠標(biāo)移到按鈕上時(shí),使用一小段代碼調(diào)用growStoryboard動(dòng)畫,將按鈕拉伸到400單位。當(dāng)用戶移動(dòng)鼠標(biāo)使其離開(kāi)按鈕時(shí),按鈕收縮到其正常尺寸。 <Storyboard x:Name="growStoryboard"> <DoubleAnimation Storyboard.TargetName="cmdGrow" Storyboard.TargetProperty="Width" To="400" Duration="0:0:1.5"></DoubleAnimation> </Storyboard> <Storyboard x:Name="revertStoryboard"> <DoubleAnimation Storyboard.TargetName="cmdGrow" Storyboard.TargetProperty="Width" Duration="0:0:3"></DoubleAnimation> </Storyboard> 現(xiàn)在,動(dòng)畫使用線性插值,這意味著按鈕以恒定的機(jī)械性的速度增長(zhǎng)和收縮。為得到更趨自然的效果,可使用緩動(dòng)函數(shù)。下面的示例添加了名為ElasticEase的緩動(dòng)函數(shù)。最終效果是按鈕彈跳出其完整寬度,然后迅速?gòu)椈匾稽c(diǎn),接著在此擺動(dòng)超出其完整尺寸(但比上一次稍少一點(diǎn)),再以稍小的幅度迅速?gòu)椈?,等等,隨著運(yùn)動(dòng)的減弱不斷地重復(fù)這一跳動(dòng)模式。之后逐漸進(jìn)入緩和的10此振蕩。Oscillations屬性控制最終跳動(dòng)的次數(shù)。ElasticEase類提供了另一個(gè)在該例中沒(méi)有使用的屬性:Springiness。該屬性的值越大,后續(xù)的每個(gè)振蕩?kù)o止得越快(默認(rèn)值是3)。 <Storyboard x:Name="growStoryboard"> <DoubleAnimation Storyboard.TargetName="cmdGrow" Storyboard.TargetProperty="Width" To="400" Duration="0:0:1.5"> <DoubleAnimation.EasingFunction> <ElasticEase Oscillations="10" EasingMode="EaseOut"></ElasticEase> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> 為真正理解該標(biāo)記和前面緩動(dòng)函數(shù)的示例之間的區(qū)別,需要試一下該動(dòng)畫。變化是顯著的。僅時(shí)候用一行XAML,就將一個(gè)簡(jiǎn)單的動(dòng)畫從業(yè)務(wù)的效果修改為精致美觀的效果,在專業(yè)的應(yīng)用程序中會(huì)感覺(jué)到這種精致效果。 二、在動(dòng)畫開(kāi)始時(shí)應(yīng)用緩動(dòng)與動(dòng)畫結(jié)束時(shí)應(yīng)用緩動(dòng) 在繼續(xù)分析不同的緩動(dòng)函數(shù)前,理解緩動(dòng)函數(shù)的應(yīng)用時(shí)機(jī)很很重要的。所有緩動(dòng)函數(shù)類都繼承自EasingFunctionBase類,并且繼承了EasingMode屬性。該屬性具有三個(gè)可能值:EaseIn(該值意味著在動(dòng)畫開(kāi)始時(shí)應(yīng)用緩動(dòng)效果)、EaseOut(該值意味著在動(dòng)畫結(jié)束時(shí)應(yīng)用緩動(dòng)效果)、EaseInOut(該值意味著在動(dòng)畫開(kāi)始和結(jié)束時(shí)應(yīng)用緩動(dòng)效果——將EaseIn用于動(dòng)畫的前半部分,將EaseOut用于動(dòng)畫的后半部分)。 在上面的示例中,growStoryboard中的動(dòng)畫使用EaseOut模式。因此,逐漸減弱的跳動(dòng)序列發(fā)生于動(dòng)畫的末尾。 如果將ElasticEase函數(shù)的緩動(dòng)模式切換為EaseIn,跳動(dòng)將在動(dòng)畫的開(kāi)始部分發(fā)生。按鈕手勢(shì)使其寬度比開(kāi)始值更小一點(diǎn),然后擴(kuò)展寬度使其超過(guò)開(kāi)始值,繼而再稍多地收縮回一點(diǎn),持續(xù)這種模式以逐漸地增加振蕩直到自由振蕩并擴(kuò)展剩余的部分(使用ElasticEase.Osicillations屬性控制振蕩次數(shù))。 最后,EaseInOut模式創(chuàng)建更新穎的效果,在動(dòng)畫的前半部分是振蕩動(dòng)畫的開(kāi)始,接下來(lái)在動(dòng)畫的后半部分是振蕩動(dòng)畫的結(jié)束。 三、緩動(dòng)函數(shù)類 WPF提供了11個(gè)緩動(dòng)函數(shù)類,所有這些類都位于熟悉的System.Windows.Media.Animation名稱控件中。下表描述了所有緩動(dòng)函數(shù)類,并列出了它們的重要屬性。請(qǐng)記住,每個(gè)緩動(dòng)函數(shù)類還提供了EasingMode屬性,用于控制是影響動(dòng)畫的開(kāi)始(EaseIn)、是影響動(dòng)畫的結(jié)束(EaseOut)還是同時(shí)影響動(dòng)畫的開(kāi)始和結(jié)束(EaseInOut)。 表 緩動(dòng)函數(shù)
許多緩動(dòng)函數(shù)提供了類似但隱約不同的效果。為成功地使用動(dòng)畫緩動(dòng),需要決定使用哪個(gè)緩動(dòng)函數(shù),以及如何進(jìn)行配置。通常,這個(gè)過(guò)程需要一點(diǎn)試錯(cuò)的體驗(yàn)。有兩個(gè)資源可提供幫助。 首先,WPF文檔為每個(gè)緩動(dòng)函數(shù)的行為提供了插圖示例,顯示動(dòng)畫如何隨著時(shí)間修改屬性值。查看這些插圖是理解緩動(dòng)函數(shù)作用的好方法。 其次,Microsoft提供了幾個(gè)范例程序,可使用這些范例播放不同的緩動(dòng)函數(shù),并嘗試不同的屬性值。最方便的范例之一是Silverlight應(yīng)用程序。 四、創(chuàng)建自定義緩動(dòng)函數(shù) 通過(guò)從EasingFunctionBase繼承自己的類,并重載EaseInCore()和CreateInstanceCore()方法,可創(chuàng)建自定義緩動(dòng)效果。這是一個(gè)非常專業(yè)的技術(shù),因?yàn)榇蟛糠珠_(kāi)發(fā)人員能通過(guò)配置標(biāo)準(zhǔn)的緩動(dòng)函數(shù)來(lái)獲得所希望的效果。然而,如果確實(shí)決定創(chuàng)建自定義緩動(dòng)函數(shù),將發(fā)現(xiàn)該過(guò)出奇簡(jiǎn)單。 需要編寫的幾乎所有邏輯都在EaseInCore()方法中運(yùn)行。該方法接受一個(gè)規(guī)范化的時(shí)間值——本質(zhì)上,是表示動(dòng)畫進(jìn)度的從0到1之間的值。當(dāng)動(dòng)畫開(kāi)始時(shí),規(guī)范化得時(shí)間值是0。它從該點(diǎn)開(kāi)始增加,直到在動(dòng)畫結(jié)束點(diǎn)達(dá)到1。 protected override double EaseInCore(double normalizedTime) {...} 在動(dòng)畫運(yùn)行期間,每次更新動(dòng)畫的值時(shí)WPF都會(huì)調(diào)用EaseInCore()方法。確切的調(diào)用頻率取決于動(dòng)畫的幀率,但可以預(yù)期每秒調(diào)用EaseInCore()方法的次數(shù)接近60。 為執(zhí)行緩動(dòng),EaseInCore()方法采用規(guī)范化的時(shí)間值,并以某種方式對(duì)其進(jìn)行調(diào)整。EaseInCore()方法返回的調(diào)整后的值,隨后被用于調(diào)整動(dòng)畫的進(jìn)度。例如,如果EaseInCore()方法返回0,動(dòng)畫被返回到其開(kāi)始點(diǎn)。如果EaseInCore()方法返回1,動(dòng)畫跳到其結(jié)束點(diǎn)。然而,EaseInCore()方法的返回值并不局限于這一范圍——例如,可返回1.5以使動(dòng)畫過(guò)渡運(yùn)行自身50%。已經(jīng)看到過(guò)用于緩動(dòng)函數(shù)(如ElasticEase)的這類效果。 下面給出的EaseInCore()方法版本根本不執(zhí)行任何工作。該版本返回規(guī)范化的時(shí)間值,意味著動(dòng)畫將均勻展開(kāi),就像是沒(méi)有緩動(dòng)。 protected override double EaseInCore(double normalizedTime) { return normalizedTime; } 下面的EaseInCore()方法版本通過(guò)計(jì)算規(guī)范化時(shí)間值得立方,復(fù)制CubicEase函數(shù)的效果。因?yàn)橐?guī)范化的時(shí)間值是小數(shù),其立方值是更小的小數(shù);所以該方法的效果是最初減慢動(dòng)作動(dòng)畫,并當(dāng)規(guī)范化的時(shí)間值(及其立方值)解決與1時(shí)導(dǎo)致動(dòng)畫加速。 protected override double EaseInCore(double normalizedTime) { return Math.Pow(normalizedTime, 3); } 最后,下面是一個(gè)執(zhí)行更有趣內(nèi)容的自定義緩動(dòng)函數(shù)——以一定的隨機(jī)量便宜規(guī)范化的時(shí)間值,導(dǎo)致分散的抖動(dòng)效果??墒褂锰峁┑腏itter依賴性屬性(在一個(gè)較小的范圍內(nèi))調(diào)整抖動(dòng)的幅度,該屬性接受從0到2000之間的數(shù)值。 public class RandomJitterEase : EasingFunctionBase { // Store a random number generator. private Random rand = new Random(); protected override double EaseInCore(double normalizedTime) { //To see the values add code like this: //System.Diagnostics.Debug.WriteLine(...); // Make sure there's no jitter in the final value. if (normalizedTime == 1) return 1; // Offset the value by a random amount. return Math.Abs(normalizedTime - (double)rand.Next(0, 10) / (2010 - Jitter)); } public int Jitter { get { return (int)GetValue(JitterProperty); } set { SetValue(JitterProperty, value); } } public static readonly DependencyProperty JitterProperty = DependencyProperty.Register("Jitter", typeof(int), typeof(RandomJitterEase), new UIPropertyMetadata(1000), new ValidateValueCallback(ValidateJitter)); private static bool ValidateJitter(object value) { int jitterValue = (int)value; return ((jitterValue <= 2000) && (jitterValue >= 0)); } // This required override simply provides a live instance of your easing function. protected override Freezable CreateInstanceCore() { return new RandomJitterEase(); } } 下面是緩動(dòng)函數(shù)在XAML中使用的示例: <Window x:Class="Animation.CustomEasingFunction" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Animation" Title="CustomEasingFunction" Height="300" Width="600"> <Window.Triggers> <EventTrigger RoutedEvent="Window.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="ellipse1" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:10"> </DoubleAnimation> <DoubleAnimation Storyboard.TargetName="ellipse2" Storyboard.TargetProperty="(Canvas.Left)" To="500" Duration="0:0:10"> <DoubleAnimation.EasingFunction> <local:RandomJitterEase EasingMode="EaseIn" Jitter="1000"></local:RandomJitterEase> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Window.Triggers> <Canvas Margin="10"> <Ellipse Name="ellipse1" Canvas.Left="0" Fill="Red" Width="20" Height="20"></Ellipse> <Ellipse Name="ellipse2" Canvas.Top="100" Canvas.Left="0" Fill="Red" Width="20" Height="20"></Ellipse> </Canvas> </Window> 效果圖如下所示,可以看到上面的圓圈平滑向右移動(dòng),下面的圓圈來(lái)回緩動(dòng)向右移動(dòng):
|
|
|