Android動畫應用之蚊香時鐘


最近學習Android動畫,做了一個小實驗——蚊香時鐘,把Android的幾種動畫操作都用上了。

首先得在res文件夾下建立anim文件夾,後面關於動畫的xml配置文件都得放在這裏。

一。首先來說說圖片上的蠟燭,他的出場涉及到Translate Animation(移動動畫)以及Scale Animation (放大縮小動畫)。

1.在anim文件夾內定義XML文件(fireout.xml)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="-500"
        android:toXDelta="0"
        android:fromYDelta="-20"
        android:toYDelta="0"
        android:duration="1000"
        android:repeatCount="0">
    </translate>
    <scale
        android:fromXScale="0.0"
        android:toXScale="1"
        android:fromYScale="0.0"
        android:toYScale="1"
        android:pivotX="0"
        android:pivotY="0"
        android:duration="1000">
    </scale>
</set>

該文件中同時包含了Translate Animation和Scale Animation ,默認兩種動畫是同時播放的,所以效果是疊加的,

也可以通過設置startOffset屬性使兩種動畫啓動時間不同

2.調用Aniamtion

在Activity的onCreate中:
ivFire =  (ImageView)findViewById(R.id.fire);
tranfireAnim = (TranslateAnimation)AnimationUtils.loadAnimation(this, R.anim.fireout);


3.啓動動畫
 ivFire.startAnimation(tranfireAnim);

蠟燭的退場效果用到了Translate Animation(移動動畫)以及Alpha Animation (透明度動畫)

firego.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">               
    <translate
         android:fromXDelta="0"
        android:toXDelta="150"
        android:fromYDelta="0"
        android:toYDelta="2"
        android:duration="1000"/>
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="1000" />
</set>

二。再說說蠟燭的火焰,火焰的跳動效果使用Frame-By-Frame Animation(幀動畫)實現的。

我們知道,Android是不支持Gif動畫的,也不建議使用Gif動畫,所以我們製作只能用多張png圖片逐幀播放的方式來實現動畫效果。

1.在anim文件夾內定義XML文件(fire.xml)
<?xml version="1.0" encoding="utf-8"?>
<animation-list  xmlns:android=  "http://schemas.android.com/apk/res/android" android:oneshot="false">
        <item android:drawable="@drawable/fire1"     android:duration="200"/>
      <item android:drawable="@drawable/fire2"       android:duration="200"/>
      <item android:drawable="@drawable/fire3"       android:duration="200"/>
      <item android:drawable="@drawable/fire4"        android:duration="200"/>
</animation-list>
每個item就是一幀,drawable是該幀顯示的圖片,duration是顯示時間,android:oneshot=”false”表示動畫一直進行,若爲true則表示只播放一次動畫。


2.調用Aniamtion(兩種方法)

ImageView ivFire = (ImageView)findViewById(R.id.fire);

可以:
fireAnimDra = (AnimationDrawable)getResources().getDrawable(R.anim.fire);
ivFire.setBackgroundDrawable(fireAnimDra);
也可以:
ivFire.setBackgroundResource(R.anim.fire);
Object backgroundObject = ivFire.getBackground();
fireAnimDra =(AnimationDrawable)backgroundObject;


3.啓動動畫

這裏獲取到了fireAnimDra,要開始動畫只需fireAnimDra.start();即可。
但是不能在Activity的onCreate()方法裏調用該方法,因爲此時AnimationDrawable類尚未完全與window接觸
另一種說法:
因爲在OnCreate()中AnimationDrawable還沒有完全的與ImageView綁定,在OnCreate()中啓動動畫,就只能看到第一張圖片。
具體我也不是很清楚。。。,誰知道的話告訴我一下。

如果希望一開始就播放動畫,可以加入一個onWindowFocusChanged()方法來啓動:
public void onWindowFocusChanged(boolean hasFocus) {
        // TODO Auto-generated method stub
    super.onWindowFocusChanged(hasFocus);
        if(hasFocus)
            animation.start();
        else
            animation.stop();
    }
也可以在onCreate中用Handler或者TimerTask延時一會兒再啓動動畫。


三。下面說說時針、分針以及秒針所使用的Rotate Animation(旋轉動畫),以時針爲例,分針、秒針只需修改duration即可


1.在anim文件夾內定義XML文件(hour.xml)
hour.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="84%"
    android:duration="43200000"
    android:repeatMode="restart"
    android:repeatCount="infinite">
</rotate>
時針旋轉一圈的時間duration被設爲43200000ms及12小時,並且這裏用到了linear_interpolator使得時針勻速旋轉


動畫的調用與啓動過程和前面的Translate Animation(移動動畫)以及Scale Animation (放大縮小動畫)是相同的,這裏就不重複了。


最後在看看蚊香的繪製以及其燃燒效果的實現

        這是我花了最長時間的部分,一開始使用PS截了一個真實蚊香的PNG圖,雖然看上去非常真實,有質感,但是發現後續的燃燒動畫效果

根本無法實現,因爲蚊香的形狀不是特別的規整,我無法獲得燃燒的路徑。關於這個路徑的獲取我也看了不少資料,有一個方法是通過

算法獲取圖片的像素點位置(具體我也不清楚),感覺太麻煩就沒研究了,最後自己用2D繪圖實現了所要的效果。

        首先我寫了一個MyView繼承了ImageView

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.ImageView;

public class MyView extends ImageView
{

    private float fRectLeft = 0;
    private float fRectTop = 0;
    private float fRectRight = 0;
    private float fRectBottom = 0;
    private float fRectLen = 0;
    private float startAngle;
    private float sweepAngle;
    private RectF rect;
    private int color = Color.BLACK;
    private long second = -1;
    private long minute;
    private float degree;
    public MyView(Context context)
    {
        super(context);
        // TODO Auto-generated constructor stub
    }
    
    public MyView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public void setColor(int color)
    {
        this.color = color;
    }
    
    @Override
    public void onDraw(Canvas c)
    {
        super.onDraw(c);
        Paint paint = new Paint();
        paint.setColor(color);
        c.drawCircle(170, 150, 10, paint);
        
        paint.setStrokeWidth(20);
        paint.setStyle(Style.STROKE);
        fRectLen = 40;
        fRectLeft = 150;
        fRectTop = 150;
        fRectRight = fRectLeft+fRectLen;
        fRectBottom = fRectTop+fRectLen;
        rect = new RectF(fRectLeft, fRectTop, fRectRight, fRectBottom);
        int i = 0;
        while(i<3)
        {
            startAngle = 270;
            sweepAngle = 90;
            c.drawArc(rect, startAngle, sweepAngle, false, paint);
            
            startAngle = 0;
            sweepAngle = 90;
            c.drawArc(rect, startAngle, sweepAngle, false, paint);
            
            fRectLeft-=20;
            fRectTop-=40;
            fRectLen+=40;
            fRectRight = fRectLeft+fRectLen;
            startAngle = 90;
            sweepAngle = 180;
            rect = new RectF(fRectLeft, fRectTop, fRectRight, fRectBottom);
            c.drawArc(rect, startAngle, sweepAngle, false, paint);
            
            fRectLeft-=20;
            fRectLen+=40;
            fRectRight = fRectLeft+fRectLen;
            fRectBottom = fRectTop+fRectLen;
            rect = new RectF(fRectLeft, fRectTop, fRectRight, fRectBottom);
            i++;
        }

        if(second>=0)
        {
            set();
            paint.setColor(Color.RED);
            c.drawArc(rect, startAngle, -6, false, paint);
            paint.setColor(Color.GRAY);
            c.drawArc(rect, 90+(minute/30 + 1 )%2*180, startAngle-( 90+(minute/30 + 1 )%2*180 ), false, paint);

            if(second<1800);
            else if(second<3600)
            {
                rect.set(50, 30, 290, 270);
                c.drawArc(rect, 270, -180, false, paint);
            }
            else if(second<5400)
            {
                rect.set(50, 30, 290, 270);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(70, 70, 270, 270);
                c.drawArc(rect, 90, -180, false, paint);    
            }
            else if(second<7200)
            {
                rect.set(50, 30, 290, 270);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(70, 70, 270, 270);
                c.drawArc(rect, 90, -180, false, paint);    
                rect.set(90, 70, 250, 230);
                c.drawArc(rect, 270, -180, false, paint);    
            }
            else if(second<9000)
            {
                rect.set(50, 30, 290, 270);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(70, 70, 270, 270);
                c.drawArc(rect, 90, -180, false, paint);    
                rect.set(90, 70, 250, 230);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(110, 110, 230, 230);
                c.drawArc(rect, 90, -180, false, paint);    
            }
            else if(second<10800)
            {
                rect.set(50, 30, 290, 270);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(70, 70, 270, 270);
                c.drawArc(rect, 90, -180, false, paint);    
                rect.set(90, 70, 250, 230);
                c.drawArc(rect, 270, -180, false, paint);    
                rect.set(110, 110, 230, 230);
                c.drawArc(rect, 90, -180, false, paint);
                rect.set(130, 110, 210, 190);
                c.drawArc(rect, 270, -180, false, paint);
            }
        }

    }
    
    public void Burn(long second)
    {
        this.second = second;
        this.minute = second/60;
        this.postInvalidate();
    }
    
    public void set()
    {
        /**
         * 0<=time<30minite            270>=degree>90            fRectLeft=50        fRectTop=30            fRectLen=12
         * 30<=time<60minte            90>=degree>-90            fRectLeft=70        fRectTop=70            fRectLen=10
         * 60<=time<90minte            270>=degree>90            fRectLeft=90        fRectTop=70            fRectLen=8
         * 90<=time<120minte        90>=degree>-90            fRectLeft=110        fRectTop=110        fRectLen=6
         * 120<=time<150minte        270>=degree>90            fRectLeft=130        fRectTop=110        fRectLen=4
         * 150<=time<180minte        90>=degree>-90            fRectLeft=150        fRectTop=150        fRectLen=2
         */
        degree = 90+(minute/30 + 1 )%2*180 - second%1800*0.1f;
        fRectLeft=50+20*(minute/30);
        fRectTop=30+Math.round(minute/60.0)*40;
        fRectLen=(12-2*(minute/30))*20;
        fRectRight = fRectLeft+fRectLen;
        fRectBottom = fRectTop+fRectLen;
        rect = new RectF(fRectLeft, fRectTop, fRectRight, fRectBottom);
        startAngle = degree;
        sweepAngle = -0.1f;
    }
}

        在MyView裏重寫了ImageView的onDraw()方法,蚊香是通過 Canvas.drawArc(rect, startAngle, sweepAngle, false, paint)畫弧線實現的;

其實我這裏的蚊香就是一個等距螺旋線,通過drawArc畫半圓,每次改變圓心和半徑,從小到大共畫了6個半圓組成一個完整的蚊香,上面的藍色代碼。

        燃燒效果也是通過drawArc來實現的,具體爲上面的紅色代碼區, public void set()根據當前時間計算出燃燒火星(蚊香上的紅色弧線)以及燒過的區域(蚊香上的灰色弧線)

的半徑,圓心,起始角度startAngle以及跨度sweepAngle,火星每秒移動0.1度。


下面給出我的Acyivity代碼,希望能幫到有需要的人


import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;

public class MosquitoRepellentIncenseActivity extends Activity
{
    /** Called when the activity is first created. */
    private MyView myIv1;
    private ImageView ivHour;
    private ImageView ivMinute;
    private ImageView ivSecond;
    private ImageView ivFire;
    private Animation Anim;
    private RotateAnimation rotateAnim;
    private AnimationDrawable fireAnimDra;
    private Timer timer;
    private long second = 0;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        myIv1 = (MyView)findViewById(R.id.MView1);
        //時針,勻速旋轉動畫
        ivHour = (ImageView)findViewById(R.id.hour);
        rotateAnim = (RotateAnimation)AnimationUtils.loadAnimation(this, R.anim.hour);
        ivHour.startAnimation(rotateAnim);
        //分針,勻速旋轉動畫
        ivMinute = (ImageView)findViewById(R.id.minute);
        rotateAnim = (RotateAnimation)AnimationUtils.loadAnimation(this, R.anim.minute);
        ivMinute.startAnimation(rotateAnim);
        //秒針,勻速旋轉動畫
        ivSecond = (ImageView)findViewById(R.id.second);
        rotateAnim = (RotateAnimation)AnimationUtils.loadAnimation(this, R.anim.second);
        ivSecond.startAnimation(rotateAnim);
        
        //蠟燭,平移動畫,縮放動畫
        //蠟燭出現
        ivFire = (ImageView)findViewById(R.id.fire);
        Anim = (Animation)AnimationUtils.loadAnimation(this, R.anim.fireout);
        Anim.setFillBefore(true);
        Anim.setFillAfter(false);
        ivFire.startAnimation(Anim);
        
        //火焰,幀動畫
        //ivFire.setBackgroundResource(R.anim.fire);
        //Object backgroundObject = ivFire.getBackground();
        //fireAnimDra = (AnimationDrawable)backgroundObject;
        
        //火焰,幀動畫
        fireAnimDra = (AnimationDrawable)getResources().getDrawable(R.anim.fire);
        ivFire.setBackgroundDrawable(fireAnimDra);
        
        //每秒更新一次蚊香燃燒進度,
        timer = new Timer();
        timer.schedule(tt, 0, 1000);//refresh once per second
    }
    
    TimerTask tt = new TimerTask()
    {
        @Override
        public void run()
        {
            // TODO Auto-generated method stub
            if(second>1)
            {
                //更新蚊香燃燒進度
                myIv1.Burn(second);
            }
            if(second==5)
            {
                //蠟燭離開
                Message msg = new Message();
                handler.sendMessage(msg);
            }
            if(second==6)
            {
                //蠟燭消失
                ivFire.setVisibility(ImageView.INVISIBLE);
            }
            second++;
        }
    };
    
    Handler handler = new Handler()
    {
        public void handleMessage(Message msg)
        {  
            //蠟燭,平移,透明度動畫
            //蠟燭離開
            Anim = (Animation)AnimationUtils.loadAnimation(MosquitoRepellentIncenseActivity.this,R.anim.firego);
            ivFire.startAnimation(Anim);
        };  
    };
    
    //讓火焰幀動畫在程序一開始就播放
    public void onWindowFocusChanged(boolean hasFocus)
    {
        // TODO Auto-generated method stub
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus)
        {
            //火焰動畫
            fireAnimDra.start();
            Log.i("TAG", "hasFocus");
        }
        else
            fireAnimDra.stop();
    }
}

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