Android中的Frame动画
本文转自:http://vlinux.iteye.com/blog/472855
相信有Android手机的人都玩过一款Kuba的游戏(没玩过的我推荐去玩一下),里面用手指接触到屏幕后产生的爆炸效果确实增加了游戏的不少色彩。那么这个是怎么做出来的呢?
很明显,这个效果应该是一个动画序列图实现的,即Frame-by-Frame动画。Android实现Frame-by-Frame动画我会的有两种方法:
1、animation-list配置,预先将一个动画按照每帧分解成的多个图片所组成的序列。然后再在Android的配置文件中将这些图片配置到动画里面。
Xml代码- <animation-listxmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <itemandroid:drawable="@drawable/explode1"android:duration="50"/>
- <itemandroid:drawable="@drawable/explode2"android:duration="50"/>
- <itemandroid:drawable="@drawable/explode3"android:duration="50"/>
- <itemandroid:drawable="@drawable/explode4"android:duration="50"/>
- </animation-list>
但是由此带来的不便也是显而易见的:drawable目录下拥挤了过多的动画帧文件。如果游戏大起来,动画效果丰富,那么drawable目录下将拥有数量庞大的图片文件,这将是开发人员的灾难(见下图)。
2、AnimationDrawable动画。其实我们发现,我们完全可以将同一动画序列的每帧图片都合并到一个大的图片中去,然后读取图片的时候按照约定好的宽、高去读就能准确的将该帧图片精确的读出来了。下图是小雪行走序列图。
将序列图读出并且转化为动画的核心代码为
- animationDrawable=newAnimationDrawable();
- Bitmap[]bitmaps=newBitmap[PlayerConst.PLAYER_XIAOXUE_WALK_FRAME];
- for(intframe=0;frame<bitmaps.length;frame++){
- Bitmapbitmap=Bitmap.createBitmap(xiaoxueWalkSerBitmap,
- frame*PlayerConst.PLAYER_XIAOXUE_WALK_WIDTH,
- lay*PlayerConst.PLAYER_XIAOXUE_WALK_HEIGHT,
- PlayerConst.PLAYER_XIAOXUE_WALK_WIDTH,
- PlayerConst.PLAYER_XIAOXUE_WALK_HEIGHT);
- animationDrawable.addFrame(newBitmapDrawable(bitmap),100);
- }//for,每层有PLAYER_XIAOXUE_WALK_FRAME帧
- animationDrawable.setOneShot(false);
- setBackgroundDrawable(animationDrawable);
具体例子可以从附件中找到。
3、SurfaceView动画。也许你很快就发现,前两个动画都必须依赖View才能展示,并且每个View只能展示一个动画。而在游戏中不可能只有一动画,更恐怖的是很多动画都是随机产生的,并不是事先约定好的,而动态创建/删除View的代价非常高,并不适合做高性能的游戏。这个时候你需要的是SurfaceView。
在SurfaceView中的动画有一点是和前边两种动画有区别的:那就是画布上所有的一切都必须自己亲自打理。在前边几个基于Animation的动画你只需关心当前动画的序列即可,其他都由系统帮你处理完毕。而在SurfaceView中,你就是那个处理程序,所有的一切包括背景都必须有你来亲自打理。
为此我写了一个框架专门来处理这个琐事,框架只有两个类:AnimationDraw和DrawRunning。其中AnimationDraw则是一个动画类,它负责描述当前动画元素的位置、当前播放到第几帧、每帧的延时是多少、是否重复播放等。
Java代码- importjava.util.Date;
- importandroid.graphics.Bitmap;
- /**
- *动画绘画元素
- *@authorvlinux
- *
- */
- publicclassAnimationDraw{
- protectedfloatx;
- protectedfloaty;
- protectedBitmap[]bitmaps;
- protectedlongduration;
- protectedLonglastBitmapTime;
- protectedintstep;
- protectedbooleanrepeat;
- /**
- *动画构造函数-for静态图片
- *@paramx:X坐标<br/>
- *@paramy:Y坐标<br/>
- *@parambitmap:显示的图片<br/>
- *@paramduration:图片显示的时间<br/>
- */
- publicAnimationDraw(floatx,floaty,Bitmapbitmap,longduration){
- Bitmap[]bitmaps={bitmap};
- this.x=x;
- this.y=y;
- this.bitmaps=bitmaps;
- this.duration=duration;
- this.repeat=true;
- lastBitmapTime=null;
- step=0;
- }
- /**
- *动画构造函数
- *@paramx:X坐标<br/>
- *@paramy:Y坐标<br/>
- *@parambitmap:显示的图片<br/>
- *@paramduration:图片显示的时间<br/>
- *@paramrepeat:是否重复动画过程<br/>
- */
- publicAnimationDraw(floatx,floaty,Bitmap[]bitmaps,longduration,booleanrepeat){
- this.x=x;
- this.y=y;
- this.bitmaps=bitmaps;
- this.duration=duration;
- this.repeat=repeat;
- lastBitmapTime=null;
- step=0;
- }
- publicBitmapnextFrame(){
- if(step>=bitmaps.length){
- //判断step是否越界
- if(!repeat){
- returnnull;
- }else{
- lastBitmapTime=null;
- }//if
- }//if
- if(null==lastBitmapTime){
- //第一次执行
- lastBitmapTime=newDate().getTime();
- returnbitmaps[step=0];
- }//if
- //第X次执行
- longnowTime=System.currentTimeMillis();
- if(nowTime-lastBitmapTime<=duration){
- //如果还在duration的时间段内,则继续返回当前Bitmap
- //如果duration的值小于0,则表明永远不失效,一般用于背景
- returnbitmaps[step];
- }//if
- lastBitmapTime=nowTime;
- returnbitmaps[step++];//返回下一Bitmap
- }
- publicfloatgetX(){
- returnx;
- }
- publicfloatgetY(){
- returny;
- }
- }
DrawRunning则是一个负责画图的线程,它是程序的核心。
Java代码- importjava.util.ArrayList;
- importjava.util.Iterator;
- importjava.util.List;
- importandroid.graphics.Bitmap;
- importandroid.graphics.Canvas;
- importandroid.view.SurfaceHolder;
- /**
- *绘画线程
- *
- *@authorvlinux
- *
- */
- publicclassDrawRunningimplementsRunnable{
- privateList<AnimationDraw>animationDraws;//所有需要画动画的集合
- privateList<AnimationDraw>buffers;//缓存前台传入需要展示的动画
- privateSurfaceHoldersurfaceHolder;
- privatebooleanrunning;
- publicDrawRunning(SurfaceHoldersurfaceHolder){
- this.surfaceHolder=surfaceHolder;
- animationDraws=newArrayList<AnimationDraw>();
- buffers=newArrayList<AnimationDraw>();
- running=true;
- }
- @Override
- publicvoidrun(){
- //TODOAuto-generatedmethodstub
- while(running){
- synchronized(surfaceHolder){
- Canvascanvas=null;
- try{
- canvas=surfaceHolder.lockCanvas(null);
- doDraw(canvas);
- }finally{
- if(null!=canvas){
- surfaceHolder.unlockCanvasAndPost(canvas);
- }//if
- }//try
- }//syn
- }//while
- }
- privatevoiddoDraw(Canvascanvas){
- synchronized(this){
- //检查缓存中是否有需要加入的动画
- if(!buffers.isEmpty()){
- animationDraws.addAll(buffers);//加入animationDraws
- buffers.clear();//清空缓存
- }//if
- }//syn
- if(animationDraws.isEmpty()){
- return;//如果animationDraws里面是空的那就不用画了
- }//if
- //---这里开始绘画
- Iterator<AnimationDraw>bombIt=animationDraws.iterator();
- while(bombIt.hasNext()){
- AnimationDrawbomb=bombIt.next();
- BitmapnextFrame=bomb.nextFrame();
- if(null==nextFrame){
- //下一Frame为null,说明动画序列已经结束
- //该动画已经完成,从动画集合中删除
- bombIt.remove();
- continue;//while
- }//if
- canvas.drawBitmap(nextFrame,bomb.getX(),bomb.getY(),null);
- }//while
- }
- publicvoidaddAnimationDraw(AnimationDrawbomb){
- synchronized(this){
- //尽量减少这个的同步响应时间,因为这个方法是前台响应的
- //多0.1秒都会直接反应到用户感知
- buffers.add(bomb);//将需要显示动画的内容加入到缓存
- }//syn
- }
- publicvoidstopDrawing(){
- running=false;
- }
- }
值得注意的是,我用了一个缓存和两个synchronized来提高前台的响应以及确保对集合类、SurfaceHolder的正确操作。
例子可以见附件。
- PlayerDemo.tar.gz (57.8 KB)
- 描述: 例子2
- 下载次数: 425
- TouchBombDemo.tar.gz (680.9 KB)
- 描述: 例子3
- 下载次数: 490
更多相关文章
- 获取相册图片及路径
- Android(安卓)5.0 技术新趋势
- android4.0.3 修改启动动画和开机声音
- Android(安卓)实现自定义的卫星式菜单(弧形菜单)View
- android 加载网络图片 SkImageDecoder::Factory returned null
- Android学习笔记19:ImageView实现图片适屏与裁剪
- Android(安卓)动画各种实现,包括帧动画、补间动画和属性动画的总
- android中-使用2D动画 — 图形处理(Canvas基础)
- Android(安卓)属性动画(一)