[WM]gif 的動畫不動怎麼辦 & 繪製進度列

在以往開發應用程式時,有時候會用到gif圖檔來做簡單的動畫效果,但是同樣的gif圖檔在裝置上卻是不動如山,這..這..這該怎麼辦..
首先要先說明一下,能夠讓裝置播放gif動畫嗎?答案是不行(路人A:來亂的阿,不行?那你接下來想講什麼)。

參考資料: Creating a Microsoft .NET Compact Framework-based Animation Control
在以往開發應用程式時,有時候會用到gif圖檔來做簡單的動畫效果,但是同樣的gif圖檔在裝置上卻是不動如山,這..這..這該怎麼辦..
首先要先說明一下,能夠讓裝置播放gif動畫嗎?答案是不行(路人A:來亂的阿,不行?那你接下來想講什麼)。只能夠去模擬出那樣的效果;如同參考的資料來源中所說,gif動畫的圖檔如果利用gif editor去開啟的話,會發現它是連續的影格(frame)組合起來的,接下來要說明的也是這樣的方式。
首先我們要先準備一張長長的圖檔,裡面就是所有的frame了,例如這邊筆者做了下面這樣的圖

每個frame是72px,所以其實要模擬出動畫的效果,就是一格一格把它切出來,之後定時更新到畫面上。
接下來就來看看程式碼的部分,首先要先加入一個"元件"到方案當中,而元件的程式碼會像下面這樣


''reference:http://msdn.microsoft.com/en-us/library/aa446483.aspx
Imports System.Drawing

Public Class AnimationCtl
    Inherits System.Windows.Forms.Control
    ''儲存要繪製的圖片
    Private _bmp As Bitmap
    ''儲存每個frame(畫格)的寬度
    Private _frameWidth As Integer
    ''儲存每個frame(畫格)的高度
    Private _frameHeight As Integer
    ''繪製圖形用
    Private _gGraphic As Graphics
    ''定時重新繪製畫面的Timer
    Private _drawTimer As Timer
    ''動畫要輪播幾次
    Private _intLoopTimes As Integer
    ''目前動畫已經輪播過的次數
    Private _intCurrentLoopCount As Integer
    ''目前繪製到哪一個frame
    Private _currentFrame As Integer
    ''總共有幾個frame
    Private _frameTotalCount As Integer

    ''' 
''' 取得或設定要繪製的圖形
'''
''' ''' ''' Public Property [Bitmap]() As Bitmap Get Return _bmp End Get Set(ByVal value As Bitmap) _bmp = value End Set End Property '''
''' 開始動畫繪製作業
'''
''' 每個frame(影格)的寬度 ''' 切換到下一個frame的延遲時間 ''' 動畫要輪播幾次 ''' Public Sub StartAnimation(ByVal fWidth As Integer, ByVal iDelay As Integer, ByVal iLoopTimes As Integer) _frameWidth = fWidth _intLoopTimes = iLoopTimes _currentFrame = 0 _frameTotalCount = _bmp.Width / _frameWidth _frameHeight = _bmp.Height Me.Size = New Size(_frameWidth, _frameHeight) _drawTimer.Interval = iDelay _drawTimer.Enabled = True End Sub '''
''' 畫面繪製動作
'''
''' ''' Private Sub Draw(ByVal iFrame As Integer) Dim pX As Integer = iFrame * _frameWidth Dim destrect As Rectangle = New Rectangle(0, 0, _frameWidth, _frameHeight) Dim rect As Rectangle = New Rectangle(pX, 0, _frameWidth, _frameHeight) Dim imgAttr As New Imaging.ImageAttributes imgAttr.SetColorKey(_bmp.GetPixel(1, 1), _bmp.GetPixel(1, 1)) 'Dim c As Color = Color.FromArgb(252, 0, 249) 'imgAttr.SetColorKey(c, c) _gGraphic.Clear(Me.Parent.BackColor) _gGraphic.DrawImage(_bmp, destrect, pX, 0, _frameWidth, _frameHeight, GraphicsUnit.Pixel, imgAttr) End Sub '''
''' 繪製frame
'''
''' Private Sub DrawFrame() If (_currentFrame < _frameTotalCount) Then _currentFrame += 1 Else _intCurrentLoopCount += 1 _currentFrame = 0 End If Draw(_currentFrame) End Sub Private Sub Timer_Tick(ByVal sender As Object, ByVal e As EventArgs) If (_intLoopTimes = -1) Then Me.DrawFrame() Else If (_intCurrentLoopCount = _intLoopTimes) Then _drawTimer.Enabled = False Else Me.DrawFrame() End If End If End Sub End Class

而初始化的地方也要做一些設定,例如下面這樣


 

而呼叫的範例會像是下面這樣的方式


 Dim A As AnimationCtl
        A = New AnimationCtl
        A.Bitmap = New Bitmap(GetAppPath() & "\test.png")
        A.Location = New Point(10, 10)
        Me.Controls.Add(A)
        A.StartAnimation(72, 300, 10)

最終執行的結果就可以在畫面上看到1~5數字的輪播了

這樣子就可以做到模擬gif動畫的效果了,而利用這個範例變化一下,來試試看畫個進度列吧,首先也是要先準備一個圖檔,筆者用盡所有美術天分搞了一張下面簡單的圖檔

之後,同樣的,在方案總管中加入一個新的元件,而元件的程式碼如下


Imports System.Drawing

Public Class myProgess
    Inherits System.Windows.Forms.Control
    ''完成度
    Private _SuccessPercent As Integer
    ''
    Private _bmp As Bitmap

    ''' 
''' 取得或設定要繪製的圖形
'''
''' ''' ''' Public Property [Bitmap]() As Bitmap Get Return _bmp End Get Set(ByVal value As Bitmap) _bmp = value End Set End Property Public Property SuccessPercent() As Integer Get Return _SuccessPercent End Get Set(ByVal value As Integer) _SuccessPercent = value DrawCtl() End Set End Property Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs) DrawCtl() MyBase.OnPaint(e) End Sub Private Sub DrawCtl() Dim g As Graphics = Me.CreateGraphics Dim destRect As New Rectangle(0, 0, Me.Width * (SuccessPercent / 100), Me.Height) Dim imgAttr As New Imaging.ImageAttributes imgAttr.SetColorKey(_bmp.GetPixel(1, 1), _bmp.GetPixel(1, 1)) g.DrawImage(_bmp, destRect, 0, 0, _bmp.Width * (SuccessPercent / 100), _bmp.Height, GraphicsUnit.Pixel, imgAttr) imgAttr.Dispose() imgAttr = Nothing g.Dispose() g = Nothing End Sub End Class

呼叫的範例會像下面這樣


Dim B As myProgess

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        B = New myProgess
        B.Bitmap = New Bitmap(GetAppPath() & "\progess.png")
        B.Location = New Point(5, 150)
        B.Size = New Size(200, 50)
        Me.Controls.Add(B)

        Timer1.Enabled = True
    End Sub

    Public Function GetAppPath() As String
        Return System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly.GetName.CodeBase.ToString)
    End Function

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        If count < 100 Then
            count += 1
        Else
            Timer1.Enabled = False
        End If
        B.SuccessPercent = count
    End Sub

這樣子,就可以"假裝"出一條新的進度列了

不過美中不足的是這樣的方式對於來源圖檔的邊邊還是沒有處理的很好(會有白邊的情形),這個等日後有機會再接續下去研究了。