1. 程式人生 > >小人快跑之WPF基礎——圖形與動畫(一)

小人快跑之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裡繪圖與動畫的基本要點。