最近學習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();
}
}