《15天玩轉WPF》—— 優美的動畫詳解(完結篇)

時間來到了 4月5日,清明已過,吾輩繼續努力 . . .
這一篇寫完之後,以後就用WPF寫一些小程序了,之後開始算法的學習 . . .

那時少年,萬花皆有逐夢時 . . .


文章目錄

  1. 何爲動畫?

  2. 簡單獨立動畫

    2.1 簡單線性動畫

    2.2 高級動畫控制

    2.3 關鍵幀動畫

    2.4 特殊的關鍵幀

    2.5 路徑動畫

  3. 場景

我們可以看出來,獨立動畫分爲很多種,如果我們可以靈活的使用這些獨立動畫,我們就可以將它們分在一個場景之中,分成一個完整的故事動畫界面 . . . (相關知識下面會講到)

首先說明一下,關鍵幀動畫是串行工作,場景是並行工作的 . . . 所以使用場景我們可以進行各種各樣的騷操作,其中 路徑動畫也是很牛逼,具體是什麼樣的,下面我們來一一講解 . . .

.


何爲動畫?

動畫自然就是 “會動的畫”。動畫的本質就是一個時間段內對象位置、角度、顏色、透明度等屬性值的連續變化。 WPF規定,可以用來製作動畫的屬性必須是依賴屬性 . . .

WPF的動畫是一種運動,這種運動的主體就是各種 UI元素,這種運動本身就是施加在 UI元素上的一些 Timeline派生類的實例加以表達,下面用到的所有動畫類型都是派生於這個類. . .

WPF把簡單動畫稱爲 AnimationTimeline,比如 目錄2.
WPF把一組協同的動畫稱爲 Storyboard(故事板,並行),比如目錄3

這裏主要是概念部分,目錄2與目錄3主要用實例來體現出這些概念 . . .

它們的派生關係圖如下:

在這裏插入圖片描述

我們可以將各種各樣的動畫派生類,組合放在 Storyboard中,這樣我們就可以看到像電影中那樣許多動畫並行工作了 . . .

.


簡單獨立動畫

我們知道,動畫就是一個屬性的值產生連續的變化而已。任何一個屬性都有自己的數據類型,比如 UIElement 的Width 和 Hight屬性爲Double類型等等。幾乎針對每個可能的數據類型,WPF動畫子系統都爲其準備了相應的動畫類型,這些動畫類型均派生自 AnimationTimeline。

它們包括:

在這裏插入圖片描述

我們發現,它們都是以base結尾,說明它們都是抽象基類。而這些抽象基類又能派生出 3 種動畫,即 簡單動畫、關鍵幀動畫、沿路徑運動的動畫,它們在我們的目錄中提到過,我們將在下面的文章一一講解。

例如 DoubleAnimationBase,它的3個派生動畫類爲:

在這裏插入圖片描述

因爲在WPF動畫系統中 Double 類型的屬性用得最多,,所以此文只講述 DoubleAnimationBase的派生類。會用這個類,其他的動畫類型則輕而易舉的掌握 . . .

下面讓我們來見識一下各種簡單的獨立動畫吧 . . .
.
.

簡單線性動畫

“簡單線性動畫” 由4個要素組成的動畫,它們分別是:

  • 變化時間(Duration 屬性):必須指定,動畫的時長多少
  • 變化終點(To 屬性):變化終點,如果沒有則採用 上一次動畫的終點默認值
  • 變化幅度(By 屬性):變化幅度,變化的規律,使用時不可使用變化終點
  • 變化起點(From 屬性):變化起點,如果沒有指定則以目標屬性的當前值爲起點

例如下面的這個例子,來演示一下簡單線性動畫的使用,XAML代碼如下:

<Button Width="60" Height="60" Content="Move" Click="Button_Click_1" 
                HorizontalAlignment="Left" VerticalAlignment="Top">
            <Button.RenderTransform>
                <TranslateTransform x:Name="tt" X="30" Y="30"/>
            < /Button.RenderTransform>
</Button>

因爲我們使用的是動畫,所以我們與動畫關聯的是 RenderTransform屬性,變形相關文章如下:
《飛天入地WPF》—— 圖形的效果與變形

當我們點擊這個按鈕時,這個按鈕會產生一個簡單的動畫:線性移動

它的事件處理如下所示:

private void Button_Click_1(object sender, RoutedEventArgs e)
{
	DoubleAnimation daX = new DoubleAnimation();
	DoubleAnimation daY = new DoubleAnimation();

	daX.From = 30;
	daY.From = 30;

	Random r = new Random();
	daX.To = r.NextDouble() * 300;
	daY.To = r.NextDouble() * 200;

	Duration duration = new Duration(TimeSpan.FromMilliseconds(500));
	daX.Duration = duration;
	daY.Duration = duration;

	this.tt.BeginAnimation(TranslateTransform.XProperty, daX);
	this.tt.BeginAnimation(TranslateTransform.YProperty, daY);
}

首先我們創建簡單動畫類型,它有着起點、終點、時長,如果我們需要指點變化幅度,則終點不需要指示,並且起點也不需要指示,例如下面:

daX.By = 100;
daY.By = 100;

//daX.From = 30;
//daY.From = 30;
//Random r = new Random();
//daX.To = r.NextDouble() * 300;
//daY.To = r.NextDouble() * 200;

最後我們使用BeginAnimation與目標的依賴屬性相關聯,並開始動畫 . . .

使用By屬性 和 To屬性的不同效果如下所示:

  • To屬性:

在這裏插入圖片描述
每次都從一個固定的起點到一個隨機的終點 . . .

  • By屬性:

在這裏插入圖片描述

每次移動都是一個固定的長度,起點爲當前目標屬性的 X、Y . . .

.

.

高級動畫控制

何爲高級? 只不過在簡單動畫的基礎上加了一點騷操作而已 . . .
其實本身並不複雜 . . .

動畫的騷操作屬性有如下這些:

屬性 描述 應用舉例
AccelerationRatio 加速速率( 0.0 - 1.0) 模擬汽車啓動
DecelerationRatio 減速速率( 0.0 - 1.0) 模擬汽車剎車
SpeedRatio 動畫實際播放速度與正常速度的比值 快進播放、慢動作
AutoReverse 從終點到起點從來一次 倒退播放
RepeatBehavior 重複行爲,取 Forever 爲永遠循環 循環播放
BeginTime 播放前的等待時間 多個動畫之前的協同
EasingFunction 緩衝式漸變 乒乓球彈跳效果

這些屬性的配合使用,可以產生很多奇妙的效果 . . .

EasingFunction 是一個擴展性非常強的屬性,它的取值是 IEasingFunction接口類型,WPF自帶的 IEasingFunction 派生類有十多種,每個派生類都能產生不同的效果,我從別處轉載了一篇相關的文章,大家可以自己試一試:
《飛天入地WPF》—— IEasingFunction的十種派生類(轉載)

我們來演示一下乒乓球的彈跳式的效果:

BounceEase bounceEase = new BounceEase();
bounceEase.Bounces = 3;
bounceEase.Bounciness = 2;
daY.EasingFunction = bounceEase;

首先我們指定起點與終點,然後我們將這個類型賦值給了 daY,它是和縱座標有關的. . .

效果如下圖所示:

在這裏插入圖片描述

還有其它幾種好玩的效果,可以自行測試 . . .

.
.

關鍵幀動畫

關鍵幀動畫允許程序員爲一段動畫設置幾個 “里程碑”,動畫執行到里程碑所在的時間點時(KeyTime被動畫控制的屬性值也必須達到設定的值(Value,這些時間線上的 “里程碑”就是關鍵幀 . . .

我們來設計一個 Button,它的動畫路線是一個 “Z”字形,如下圖所示:

在這裏插入圖片描述

如果我們不用關鍵幀將會非常的痛苦,需要用到 BeginTime或者 Complete,此處不再多講 . . .

下面我們來實現這一個小例子 . . .

XAML代碼如下:

<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top"
                Width="80" Height="80" Click="Button_Click">
            <Button.RenderTransform>
                <TranslateTransform x:Name="tt" X="0" Y="0"/>
            </Button.RenderTransform>
</Button>

點擊按鈕開始執行動畫,事件處理器如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
	// 創建動畫
	DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();
	DoubleAnimationUsingKeyFrames dakY = new DoubleAnimationUsingKeyFrames();
	// 動畫時長
	dakX.Duration = new Duration(TimeSpan.FromMilliseconds(900));
	dakY.Duration = new Duration(TimeSpan.FromMilliseconds(900));
	// 創建關鍵幀
	LinearDoubleKeyFrame x_kf_1 = new LinearDoubleKeyFrame();
	LinearDoubleKeyFrame x_kf_2 = new LinearDoubleKeyFrame();
	LinearDoubleKeyFrame x_kf_3 = new LinearDoubleKeyFrame();
	// 爲關鍵幀賦值
	x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
	x_kf_1.Value = 200;
	x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
	x_kf_2.Value = 0;
	x_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
	x_kf_3.Value = 200;
	// 給動畫加入關鍵幀
	dakX.KeyFrames.Add(x_kf_1);
	dakX.KeyFrames.Add(x_kf_2);
	dakX.KeyFrames.Add(x_kf_3);

	// 與上面一樣
	LinearDoubleKeyFrame y_kf_1 = new LinearDoubleKeyFrame();
	LinearDoubleKeyFrame y_kf_2 = new LinearDoubleKeyFrame();
	LinearDoubleKeyFrame y_kf_3 = new LinearDoubleKeyFrame();
	y_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
	y_kf_1.Value = 0;
	y_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
	y_kf_2.Value = 180;
	y_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
	y_kf_3.Value = 180;
	dakY.KeyFrames.Add(y_kf_1);
	dakY.KeyFrames.Add(y_kf_2);
	dakY.KeyFrames.Add(y_kf_3);

	// 關聯屬性,開始動畫
	this.tt.BeginAnimation(TranslateTransform.XProperty, dakX);
	this.tt.BeginAnimation(TranslateTransform.YProperty, dakY);
}

其中下面的這段代碼:

x_kf_1.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(300));
x_kf_1.Value = 200;
x_kf_2.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(600));
x_kf_2.Value = 0;
x_kf_3.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(900));
x_kf_3.Value = 200;

他的意思是動畫到了 KeyTime時,它的值就必須到達 Value

程序會自動計算兩個關鍵幀之間的關係,移動軌跡 . . .

效果如下圖所示:

在這裏插入圖片描述

我們可以用關鍵幀做出各種各樣的有規律的線性運行 . . .

.
.

特殊的關鍵幀

DoubleAnimationUsingKeyFrames 的 KeyFrames 屬性的數據類型是 DoubleKeyFrameCollection,此集合類可接收的類型爲 DoubleKeyFrame。DoubleKeyFrame 是一個抽象類 . . .

它的所有派生類如下:

  • LinearDoubleKeyFrame:線性變化關鍵幀(均勻的變化,上面已經使用過了 . . .)
  • DiscreteDoubleKeyFrame:不連續變化關鍵幀,跳躍性的變化
  • SplineDoubleKeyFrame:樣條函數式變化關鍵幀,目標屬性的變化速率是貝塞爾曲線
  • EasingDoubleKeyFrame:緩衝式變化關鍵幀,目標屬性以某種緩衝形式變化

.
上面有一個類型我用紅色標記了,爲什麼呢 ? 因爲這個類型非常的常用,它可以取代
LinearDoubleKeyFrame . . .

SplineDoubleKeyFrameKeySpline屬性可以控制速率的變化(變化速率是貝塞爾曲線)

控制點有兩個:ControlPoint1 與 ControlPoint2 . . .
.
控制點速率的各種取值樣式有如下的各種可能:
在這裏插入圖片描述

.
.
下面我們來用 SplineDoubleKeyFrame 取代上個例子的 LinearDoubleKeyFrame,完成同樣的Button “Z”型移動動畫,只不過加了一個速率的變化 . . .

XAML代碼如下:

<Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top"
                Width="80" Height="80" Click="Button_Click">
            <Button.RenderTransform>
                <TranslateTransform x:Name="tt" X="0" Y="0"/>
            </Button.RenderTransform>
</Button>

事件處理器如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
	// 創建動畫類型爲關鍵幀類型
	DoubleAnimationUsingKeyFrames dakX = new DoubleAnimationUsingKeyFrames();
	dakX.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
	DoubleAnimationUsingKeyFrames dakY = new DoubleAnimationUsingKeyFrames();
	dakY.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
            
	// 創建關鍵針,和上面的例子差不多
	SplineDoubleKeyFrame kf1 = new SplineDoubleKeyFrame();
	kf1.KeyTime = KeyTime.FromPercent(0.33);
	kf1.Value = 200;
	SplineDoubleKeyFrame kf2 = new SplineDoubleKeyFrame();
	kf2.KeyTime = KeyTime.FromPercent(0.66);
	kf2.Value = 0;
	SplineDoubleKeyFrame kf3 = new SplineDoubleKeyFrame();
	kf3.KeyTime = KeyTime.FromPercent(1);
	kf3.Value = 200;
	SplineDoubleKeyFrame kf4 = new SplineDoubleKeyFrame();
	kf4.KeyTime = KeyTime.FromPercent(0.33);
	kf4.Value = 0;
	SplineDoubleKeyFrame kf5 = new SplineDoubleKeyFrame();
	kf5.KeyTime = KeyTime.FromPercent(0.66);
	kf5.Value = 180;
	SplineDoubleKeyFrame kf6 = new SplineDoubleKeyFrame();
	kf6.KeyTime = KeyTime.FromPercent(1);
	kf6.Value = 180;

	// 設置速率(貝塞爾曲線的速率,s型)
	KeySpline ks = new KeySpline();
	ks.ControlPoint1 = new Point(0, 1);
	ks.ControlPoint2 = new Point(1, 0);
	kf1.KeySpline = ks;
	kf2.KeySpline = ks;
	kf3.KeySpline = ks;
	kf4.KeySpline = ks;
	kf5.KeySpline = ks;
	kf6.KeySpline = ks;

	// 加入到動畫之中
	dakX.KeyFrames.Add(kf1);
	dakX.KeyFrames.Add(kf2);
	dakX.KeyFrames.Add(kf3);
	dakY.KeyFrames.Add(kf4);
	dakY.KeyFrames.Add(kf5);
	dakY.KeyFrames.Add(kf6);

	// 爲了效果更加清楚,加一個倒退、循環效果
	dakX.AutoReverse = true;
	dakY.AutoReverse = true;
	dakX.RepeatBehavior = RepeatBehavior.Forever;
	dakY.RepeatBehavior = RepeatBehavior.Forever;

	// 關聯屬性,開始動畫
	this.tt.BeginAnimation(TranslateTransform.XProperty, dakX); 
	this.tt.BeginAnimation(TranslateTransform.YProperty, dakY); 
}

我們發現這個事件處理器與上面那個例子的事件處理器並沒有什麼大的差別,只不過加了一個控制速率而已 . . .

效果圖如下:

在這裏插入圖片描述

他的移動速率是不是變化了呢?

.
.

路徑動畫

如果我們想讓目標沿着一條給定的路徑移動,我們可以用 DoubleAnimationUsingPath。

DoubleAnimationUsingPath 需要一個 PathGeometry來指明移動路徑,Path不行,
DoubleAnimationUsingPath 有一個重要的屬性爲 Source,它的取值是 枚舉類型 PathAnimationSource的值,X、Y、Angle . . .

下面我們來試一下路徑動畫,我們讓一個Button 沿着一條貝塞爾曲線做來回的運動 . . .

XAML代碼如下:

<Grid x:Name="LayoutRoot">
        <Grid.Resources>
            <PathGeometry x:Key="movingPath" 
            	Figures="m 0,150 c 300,-100 300,400 600,120"/>
        </Grid.Resources>
        
        <Button Content="Move" HorizontalAlignment="Left" VerticalAlignment="Top"
                Width="80" Height="80" Click="Button_Click">
            <Button.RenderTransform>
                <TranslateTransform x:Name="tt" X="0" Y="150"/>
            </Button.RenderTransform>
        </Button>
</Grid>

我們將 PathGeometry 作出 LayoutRoot的資源,通過這個名稱我們可以查找到這個資源 . . .

事件處理器如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
	// 查找資源
	PathGeometry pathGeometry = this.LayoutRoot.FindResource("movingPath")
	 	as PathGeometry;

	// 設置動畫時間
	Duration duration = new Duration(TimeSpan.FromMilliseconds(600));
	
	// 創建路徑動畫
	DoubleAnimationUsingPath dmpX = new DoubleAnimationUsingPath();
	dmpX.Duration = duration;
	dmpX.PathGeometry = pathGeometry;
	dmpX.Source = PathAnimationSource.X;
	
	DoubleAnimationUsingPath dmpY = new DoubleAnimationUsingPath();
	dmpY.Duration = duration;
	dmpY.PathGeometry = pathGeometry;
	dmpY.Source = PathAnimationSource.Y;
	
	// 設置來回循環效果
	dmpX.AutoReverse = true;
	dmpY.AutoReverse = true;
	dmpX.RepeatBehavior = RepeatBehavior.Forever;
	dmpY.RepeatBehavior = RepeatBehavior.Forever;
	
	// 動畫關聯屬性
	this.tt.BeginAnimation(TranslateTransform.XProperty, dmpX);
	this.tt.BeginAnimation(TranslateTransform.YProperty, dmpY);
}

這個例子比上面的例子簡單多了,而且也比較騷氣,只要你給出路徑,他就能按照你的意願來 . . .

.
.


場景

之前的關鍵幀動畫是一種串行的動畫,而場景則是並行的 . . .

每一個獨立的個體都有自己的工作,在某一時刻他們同時工作,就能提高很多的效率,比較拍電影的時候,很多人同時在工作. . .
.
設計WPF的場景時情況也差不多,操作過程爲:

  1. 先是把一組獨立的動畫組織在一個 Storyboard元素中
  2. 指定哪個動畫由哪個 UI元素、哪個屬性負責完成
  3. Storyboard 擁有一個觸發時機(本例放在 Triggers中

下面我們來完成三個小球比賽的小例子 . . .
.

XMAL界面佈局如下:

<Grid Background="Black">
        <Grid.RowDefinitions>
            <RowDefinition Height="38"/>
            <RowDefinition Height="38"/>
            <RowDefinition Height="38"/>
        </Grid.RowDefinitions>
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="60"/>
        </Grid.ColumnDefinitions>
        
        <Border BorderBrush="Gray" BorderThickness="1" Grid.Row="0">
            <Ellipse x:Name="ballR" Height="36" Width="36" Fill="Red"
                     HorizontalAlignment="Left">
                <Ellipse.RenderTransform>
                    <TranslateTransform x:Name="ttR"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Border>
        
        <Border BorderBrush="Gray" BorderThickness="1,0,1,1" Grid.Row="1">
            <Ellipse x:Name="ballG" Height="36" Width="36" Fill="LawnGreen"
                     HorizontalAlignment="Left">
                <Ellipse.RenderTransform>
                    <TranslateTransform x:Name="ttG"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Border>
        
        <Border BorderBrush="Gray" BorderThickness="1,0,1,1" Grid.Row="2">
            <Ellipse x:Name="ballB" Height="36" Width="36" Fill="Blue"
                     HorizontalAlignment="Left">
                <Ellipse.RenderTransform>
                    <TranslateTransform x:Name="ttB"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Border>
        <Button Content="Go!" Grid.Column="1" Grid.RowSpan="3"/>
</Grid>

我們爲三個小球進行了相同的佈局,並且 將他們的 TranslateTransform 定義名稱 . . .
還有一個觸發動畫的按鈕 . . .

界面效果如下:

在這裏插入圖片描述

當我們點擊按鈕時,它們就一起移動.
因爲在C#中寫 Storyboard會比較麻煩,所以我們直接將代碼寫在 Button的 Triggers中 . . .

Triggers 代碼如下:

<Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard>
            <Storyboard Duration="0:0:5">
                
                <DoubleAnimation Duration="0:0:5" To="685"
                                 Storyboard.TargetName="ttR"
                                 Storyboard.TargetProperty="X"/>
                
                <DoubleAnimationUsingKeyFrames Duration="0:0:5"
                        Storyboard.TargetName="ttG" Storyboard.TargetProperty="X">
                    <SplineDoubleKeyFrame KeyTime="0:0:5" Value="685"
                                          KeySpline="0,1,1,0"/>
                </DoubleAnimationUsingKeyFrames>
                
                <DoubleAnimationUsingKeyFrames Duration="0:0:5"
                        Storyboard.TargetName="ttB" Storyboard.TargetProperty="X">
                    <SplineDoubleKeyFrame KeyTime="0:0:5" Value="685"
                                          KeySpline="1,0,0,1"/>
                </DoubleAnimationUsingKeyFrames>
            
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Button.Triggers>

有一個是簡單動畫,其他兩是關鍵幀動畫

每一個動畫都關聯了對應的目標名稱與屬性,例如下面的代碼:

Storyboard.TargetName="ttR"
Storyboard.TargetProperty="X"

它們目標是紅色的小球,屬性是紅色小球的 X座標,所以當我們執行動畫時,他會一直移動 . . .

效果圖如下所示:

在這裏插入圖片描述

.
.


作者:浪子花夢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章