`
vlinux
  • 浏览: 52300 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

Android中的Frame动画

阅读更多

相信有Android手机的人都玩过一款Kuba的游戏(没玩过的我推荐去玩一下),里面用手指接触到屏幕后产生的爆炸效果确实增加了游戏的不少色彩。那么这个是怎么做出来的呢?

 

很明显,这个效果应该是一个动画序列图实现的,即Frame-by-Frame动画。Android实现Frame-by-Frame动画我会的有两种方法:

 

1、animation-list配置,预先将一个动画按照每帧分解成的多个图片所组成的序列。然后再在Android的配置文件中将这些图片配置到动画里面。

 

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/explode1" android:duration="50" />
    <item android:drawable="@drawable/explode2" android:duration="50" />
    <item android:drawable="@drawable/explode3" android:duration="50" />
    <item android:drawable="@drawable/explode4" android:duration="50" />
</animation-list>

 

但是由此带来的不便也是显而易见的:drawable目录下拥挤了过多的动画帧文件。如果游戏大起来,动画效果丰富,那么drawable目录下将拥有数量庞大的图片文件,这将是开发人员的灾难(见下图)。


 

2、AnimationDrawable动画。其实我们发现,我们完全可以将同一动画序列的每帧图片都合并到一个大的图片中去,然后读取图片的时候按照约定好的宽、高去读就能准确的将该帧图片精确的读出来了。下图是小雪行走序列图。



 将序列图读出并且转化为动画的核心代码为

animationDrawable = new AnimationDrawable();
Bitmap[] bitmaps = new Bitmap[PlayerConst.PLAYER_XIAOXUE_WALK_FRAME];
for (int frame = 0; frame < bitmaps.length; frame++) {
	Bitmap bitmap = 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(new BitmapDrawable(bitmap),100);
}// for,每层有 PLAYER_XIAOXUE_WALK_FRAME 帧
animationDrawable.setOneShot(false);
setBackgroundDrawable(animationDrawable);

 具体例子可以从附件中找到。

 

3、SurfaceView动画。也许你很快就发现,前两个动画都必须依赖View才能展示,并且每个View只能展示一个动画。而在游戏中不可能只有一动画,更恐怖的是很多动画都是随机产生的,并不是事先约定好的,而动态创建/删除View的代价非常高,并不适合做高性能的游戏。这个时候你需要的是SurfaceView。

 

在SurfaceView中的动画有一点是和前边两种动画有区别的:那就是画布上所有的一切都必须自己亲自打理。在前边几个基于Animation的动画你只需关心当前动画的序列即可,其他都由系统帮你处理完毕。而在SurfaceView中,你就是那个处理程序,所有的一切包括背景都必须有你来亲自打理。

 

为此我写了一个框架专门来处理这个琐事,框架只有两个类:AnimationDraw和DrawRunning。其中AnimationDraw则是一个动画类,它负责描述当前动画元素的位置、当前播放到第几帧、每帧的延时是多少、是否重复播放等。

 

import java.util.Date;

import android.graphics.Bitmap;

/**
 * 动画绘画元素
 * @author vlinux
 *
 */
public class AnimationDraw {

	protected float x;
	protected float y;
	protected Bitmap[] bitmaps;
	protected long duration;
	
	protected Long lastBitmapTime;
	protected int step;
	protected boolean repeat;
	
	/**
	 * 动画构造函数-for静态图片
	 * @param x:X坐标<br/>
	 * @param y:Y坐标<br/>
	 * @param bitmap:显示的图片<br/>
	 * @param duration:图片显示的时间<br/>
	 */
	public AnimationDraw(float x, float y, Bitmap bitmap, long duration) {
		Bitmap[] bitmaps = {bitmap};
		this.x = x;
		this.y = y;
		this.bitmaps = bitmaps;
		this.duration = duration;
		this.repeat = true;
		lastBitmapTime = null;
		step = 0;
	}
	
	/**
	 * 动画构造函数
	 * @param x:X坐标<br/>
	 * @param y:Y坐标<br/>
	 * @param bitmap:显示的图片<br/>
	 * @param duration:图片显示的时间<br/>
	 * @param repeat:是否重复动画过程<br/>
	 */
	public AnimationDraw(float x, float y, Bitmap[] bitmaps, long duration, boolean repeat) {
		this.x = x;
		this.y = y;
		this.bitmaps = bitmaps;
		this.duration = duration;
		this.repeat = repeat;
		lastBitmapTime = null;
		step = 0;
	}
	
	
	public Bitmap nextFrame() {

		if (step >= bitmaps.length) {
			// 判断step是否越界
			if( !repeat ) {
				return null;
			} else {
				lastBitmapTime = null;
			}//if
		}// if

		if (null == lastBitmapTime) {
			// 第一次执行
			lastBitmapTime = new Date().getTime();
			return bitmaps[step = 0];
		}// if

		// 第X次执行
		long nowTime = System.currentTimeMillis();
		if (nowTime - lastBitmapTime <= duration) {
			// 如果还在duration的时间段内,则继续返回当前Bitmap
			// 如果duration的值小于0,则表明永远不失效,一般用于背景
			return bitmaps[step];
		}// if
		lastBitmapTime = nowTime;
		return bitmaps[step++];// 返回下一Bitmap
	}
	
	public float getX() {
		return x;
	}

	public float getY() {
		return y;
	}
	
}
 

DrawRunning则是一个负责画图的线程,它是程序的核心。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.SurfaceHolder;

/**
 * 绘画线程
 * 
 * @author vlinux
 * 
 */
public class DrawRunning implements Runnable {

	private List<AnimationDraw> animationDraws;//所有需要画动画的集合
	private List<AnimationDraw> buffers;//缓存前台传入需要展示的动画
	private SurfaceHolder surfaceHolder;
	private boolean running;

	public DrawRunning(SurfaceHolder surfaceHolder) {
		this.surfaceHolder = surfaceHolder;
		animationDraws = new ArrayList<AnimationDraw>();
		buffers = new ArrayList<AnimationDraw>();
		running = true;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (running) {
			synchronized (surfaceHolder) {
				Canvas canvas = null;
				try {
					canvas = surfaceHolder.lockCanvas(null);
					doDraw(canvas);
				} finally {
					if (null != canvas) {
						surfaceHolder.unlockCanvasAndPost(canvas);
					}// if
				}// try
			}// syn
		}// while
	}

	private void doDraw(Canvas canvas) {
		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()) {
			AnimationDraw bomb = bombIt.next();
			Bitmap nextFrame = bomb.nextFrame();
			if (null == nextFrame) {
				//下一Frame为null,说明动画序列已经结束
				//该动画已经完成,从动画集合中删除
				bombIt.remove();
				continue;//while
			}// if
			canvas.drawBitmap(nextFrame, bomb.getX(), bomb.getY(), null);
		}// while
	}

	public void addAnimationDraw(AnimationDraw bomb) {
		synchronized(this) {
			//尽量减少这个的同步响应时间,因为这个方法是前台响应的
			//多0.1秒都会直接反应到用户感知
			buffers.add(bomb);//将需要显示动画的内容加入到缓存
		}//syn
	}

	public void stopDrawing() {
		running = false;
	}

}

 

值得注意的是,我用了一个缓存和两个synchronized来提高前台的响应以及确保对集合类、SurfaceHolder的正确操作。

例子可以见附件。

  • 大小: 8 KB
  • 描述: 小雪行走序列图
  • 大小: 38 KB
分享到:
评论
13 楼 yinhongbiao 2012-10-25  
刚好 要用到这种效果 ,帮助很大啊。 谢谢
12 楼 mingfeng002 2012-03-02  
两个synchronized这样的好处是什么?已经有了canvas = surfaceHolder.lockCanvas(null);这样两次会产生影响吗
11 楼 mylazygirl 2010-12-21  
初学Android,您的这篇文章对我帮助非常大,非常感谢。
10 楼 1986zzrobin 2010-04-19  
"SurfaceView动画。也许你很快就发现,前两个动画都必须依赖View才能展示,并且每个View只能展示一个动画。而在游戏中不可能只有一动画"


View 实现线程类,在局部刷新可以实现的,SurfaceView很多介绍的是双缓冲,不太了解。请博主解释下。
9 楼 稻-草 2010-01-30  
不错,我也是这么想的
8 楼 zhuixinjian 2009-12-31  
vlinux 写道
zhuixinjian 写道
回家仔细研究了下你这段代码。

你每次都是在一个新点画一连串的动画,那么物体移动的动画肯定不行的。

应该是每张图片都带着各自的新坐标来移动。

// 第X次执行  
        long nowTime = System.currentTimeMillis();  
        if (nowTime - lastBitmapTime <= duration) {  
            // 如果还在duration的时间段内,则继续返回当前Bitmap  
            // 如果duration的值小于0,则表明永远不失效,一般用于背景  
            return bitmaps[step];  
        }// if


这段代码也控制不了,是不是永久不消失。事实上小于0的时候,他直接跳出来,返回下一站图片了。

是的,例子代码在控制逻辑上有BUG,这是我当时没考虑清楚。我今天找个时间吧他Fixed掉



呵呵。我对线程编程有点弱,我做了写修改,只是把每张图片都带上新坐标了,实现了物体移动,却不知道怎么能拿到最新的坐标。

以至于每次事件触发,都是从原始起点到终点画了一遍
7 楼 vlinux 2009-12-31  
zhuixinjian 写道
回家仔细研究了下你这段代码。

你每次都是在一个新点画一连串的动画,那么物体移动的动画肯定不行的。

应该是每张图片都带着各自的新坐标来移动。

// 第X次执行  
        long nowTime = System.currentTimeMillis();  
        if (nowTime - lastBitmapTime <= duration) {  
            // 如果还在duration的时间段内,则继续返回当前Bitmap  
            // 如果duration的值小于0,则表明永远不失效,一般用于背景  
            return bitmaps[step];  
        }// if


这段代码也控制不了,是不是永久不消失。事实上小于0的时候,他直接跳出来,返回下一站图片了。

是的,例子代码在控制逻辑上有BUG,这是我当时没考虑清楚。我今天找个时间吧他Fixed掉
6 楼 zhuixinjian 2009-12-30  
回家仔细研究了下你这段代码。

你每次都是在一个新点画一连串的动画,那么物体移动的动画肯定不行的。

应该是每张图片都带着各自的新坐标来移动。

// 第X次执行  
        long nowTime = System.currentTimeMillis();  
        if (nowTime - lastBitmapTime <= duration) {  
            // 如果还在duration的时间段内,则继续返回当前Bitmap  
            // 如果duration的值小于0,则表明永远不失效,一般用于背景  
            return bitmaps[step];  
        }// if


这段代码也控制不了,是不是永久不消失。事实上小于0的时候,他直接跳出来,返回下一站图片了。
5 楼 qdsjj2000 2009-12-17  
佩服啊,高手啊,五体投地啊
4 楼 哇你长得真高 2009-10-20  
好厉害


3 楼 buyajun 2009-10-20  
楼主写的 这几篇 都是 精华中的精华

期待 楼主 更多 佳作 出现
2 楼 healthjava 2009-10-09  
受益匪浅,顶博主。
看楼主的代码里,所有的bitmap都在SurfaceAnimationView构造函数中实例化,这一点似乎不妥。
1 楼 raymondlueng 2009-09-21  
早看到您这文章,我就可以少走很多弯路了,非常感谢!

相关推荐

Global site tag (gtag.js) - Google Analytics