小人快跑之WPF基礎——圖形與動畫(一)
前言:
之前為了完成一些任務,因為公司只能限定一些網路訪問且不能用儲存裝置進行拷貝,所以自己在家裡寫了一個demo就放到csdn的下載區回公司再下下來,令人意外的是這個非常小的demo居然兩天裡就有幾十個下載還得到了幾個評論(在我之前從來沒有遇到過的)。想了一下乾脆分享一下自己的學習心得好了。
接下來讓我們初入WPF動畫的世界吧
1.任務概述:
使用WPF模擬一個小人走路的動畫效果。
之前沒有過WPF開發的經驗,但是拿到東西后第一反應就是利用多幅圖片來反覆播放,如果幀率足夠的話就能完成這個事情了,所以撇開技術不說,任務實際上是非常簡單的,因為我們已經知道要怎麼做了,有思路了。
2.本次關鍵知識的儲備
2.1 我們需要知道Image標籤,如何繪圖,插入圖片以及後臺執行緒(其實就是一個控制元件與三個簡單的方法)
2.2 WPF基於幀的動畫:CompositionTarget.Rendering += new EventHandler(DoSomething)
基於幀的動畫是WPF提供的一套非常低階的方法(不是指功能低階,而是要自己處理比較多的內容),這種方法只需要呼叫 CompositionTarget.Rendering這個靜態時間,它是WPF系統預設提供給我們的事件,觸發這個事件會完成一件事情,就是在WPF在組合樹呈現之前瞬間渲染要顯示的圖形或者影象,非常方便。
其實就是程式反覆的觸發這個事件幫我們重新整理圖片,MSDN上說是以每秒60幀的速度,而想要有動畫效果,讓圖片以一定的幀率反覆重新整理多張變動比較小的圖片就可以了。
例如我提供的這兩張圖片(臨時手畫的)
具體的程式碼如下:
//儲存需要展示的圖片數量 ObservableCollection<BitmapImage> bmList; //記錄索引,用來重複切換圖片 int index = 0; public MainWindow() { InitializeComponent(); InitList(); //呼叫系統預設的幀進行動畫 CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering); }
繪製圖片:(注:GetImagePath是我寫好的一個方法,主要作用是用來找圖片地址的,稍後的原始碼連結裡面大家可以自己去下載下來看)
public void InitList()
{
bmList = new ObservableCollection<BitmapImage>();
//圖片是按照1,2,3這樣的命名規則,所以迴圈新增圖片
for (int i = 1; i < 3; i++)
{
var path = GetImagePath(i);
//在UI上繪製圖片
BitmapImage bmImg = new BitmapImage(new Uri(path));
bmList.Add(bmImg);
}
}
下面是觸發的具體方法(插入圖片):
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (index < bmList.Count)
{
this.img1.Source = bmList[index];
this.img1.Width = this.img1.Source.Width;
this.img1.Height = this.img1.Source.Height;
ImgMove();
index++;
}
else
{
index = 0;
}
}
}
XAML的程式碼:只有一句話,是一個Image標籤控制元件
<Canvas Grid.Row="0">
<Image Height="150" Width="100" x:Name="img1"></Image>
</Canvas>
通過上面的工作,實際上就已經完成了動畫的效果了,就是對img1反覆的賦值,再反覆繪圖,這樣呈現出來的效果就是一個動畫了。
但是現在有一些問題,幀率太快了,重新整理的速度大大超過了我圖片的數量(幀數是60/s,而我只是做一個demo所以只有兩個圖片),完全看不見友好的效果(你只能看見狂閃),所以還要稍微控制一下幀速,怎麼控制呢?這個方法好像也沒有提供屬性或者方法呼叫,只好自己傻瓜一點去寫了,在後臺開一個執行緒,sleep等待一下。
之前的程式碼稍微修改一下
//儲存需要展示的圖片數量
ObservableCollection<BitmapImage> bmList;
//記錄索引,用來重複切換圖片
int index = 0;
//是否重新整理幀
bool isRendering = false;
public MainWindow()
{
InitializeComponent();
InitList();
//呼叫系統預設的幀進行動畫
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
OpenBackgroundWork();
}
/// <summary>
/// 開啟一個後臺執行緒,用來控制幀頻率的重新整理速度
/// </summary>
private void OpenBackgroundWork()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(
(sender, e) =>
{
while (true)
{
isRendering = true;
System.Threading.Thread.Sleep(100); //停100毫秒
}
});
bw.RunWorkerAsync();
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (isRendering)
{
if (index < bmList.Count)
{
this.img1.Source = bmList[index];
this.img1.Width = this.img1.Source.Width;
this.img1.Height = this.img1.Source.Height;
ImgMove();
index++;
}
else
{
index = 0;
}
isRendering = false;
}
}
此外,再計算一下Image的移動,每次在canvas畫布上走動一小段距離模擬出行走的效果
//設定畫板上圖片的初始座標;
Point location = new Point(0, 0);
/// <summary>
/// 利用座標算出圖片的位置,每次在橫軸上增加50px,顯示讓圖片往前走的效果
/// </summary>
private void ImgMove()
{
Canvas.SetLeft(img1, location.X);
location.X = location.X + 50;
//超過邊界就回原點
if (location.X > this.Width)
{
location.X = 0;
}
}
做完這些工作之後,再執行程式,效果就比較明顯了。
3.小結
總結一下,利用WPF的基於幀動畫的方法,我們不需要儲備太多知識,只需要明白(1)如何插入與繪製圖片,(2)Image標籤能夠被插入圖片再加一個CompositionTarget.Rendering的靜態事件就能夠解決。
但前面說到了,這是一個低階方法,簡單之外,也有它不好的地方,(1)我們要自己繪製大量圖片 (2) 手動控制幀速 (3)幀動畫事件是一個反覆迴圈觸發的事件,它沒有非常好的時間線概念,什麼時間結束這個動畫,是需要自己做額外的處理。而且在意外忘記移除掉這個事件的話,會對整個程式造成不小的效能影響
綜上所述,基於幀的動畫,需要程式設計師自己處理大量細節邏輯,並且控制不好的話,效果和效能上都不足,且維護起來較麻煩,接下來,我還會使用其他兩種不同的方案完成這個任務並簡單介紹一下WPF裡繪圖與動畫的基本要點。