Android手机游戏开发学习笔记
第一部分 其实游戏就是让状态机不断的让Canvas在View上画你想要的东西。这个状态机包括内部的执行,还包括外部的输入。
Android开发的MVC模式
1,通过View和SurfaceView来显示界面的视图。(处理界面与用户的交互事件,如,触笔点击,用户按键等。可通过View类的onKeyDown,onKeyUp,onTouchEvent等)。
2,用Activity来控制游戏的整体结构。
3,设计一个逻辑类,用来处理逻辑运算。
Android中任何一个View类都只有重写onDraw方法来实现界面显示。
Android中提供了onKeyUp,onKeyDown,onKeyMultiple,onKeyPreIme,onTouchEvent,onTrackballEvent等方法。可以用来处理游戏中的事件消息。所以继承View时,需要重载这些方法。
Android中提供了invalidate来刷新界面,但invalidate不能直接在线程中调用,违背单线程模型。
因此Android中最常用的方法是利用Handler来时更新UI界面。
第一部分 View 类
每个View类都有一个绘画的画布,在游戏中可以自定义视图View,任何一个View类都只需要重写onDraw方法来实现界面显示,可以是3D,也可以是文本。
游戏的核心就是不断的绘图和刷新,图我们可以通过onDraw方法绘制,刷新Android中可以用invalidate方法来刷新界面,注意:invalidate不能直接在线程中调用,因其违背了
违背单线程模型。因此Android中最常用的方法是利用Handler来时更新UI界面。下面这个例子中包含了两个刷新方法。
public class Game extends Activity{
public static final int REFRESH = 1;
public GameView gameView;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.gameView = new GameView(this);//实例化GameView
setContentView(gameView);
new Thread(new GameThread()).start();
}
// Handler handler = new Handler(){//注释掉的为实例化Handler对象并重写handleMessage方法实现一个消息接受器,然后在线程中通过sendMessage方法发送更新界面的消息,
当接收器收到更新界面的消息时,便执行invalidate方法更新屏幕显示。
// public void handleMessage(Message msg){//接受消息
// switch (msg.what){
// case Game.REFRESH:
// gameView.invalidate();//更新界面
// break;
// }
// super.handleMessage(msg);
// }
// };
public boolean onTouchEvent (MotionEvent event){
return true;
}
public boolean onKeyDown(int keyCode,KeyEvent event){
return true;
}
public boolean onKeyUp(int keyCode,KeyEvent event){
switch (keyCode){
case KeyEvent.KEYCODE_DPAD_DOWN:
gameView.y+=6;
break;
case KeyEvent.KEYCODE_DPAD_UP:
gameView.y-=6;
break;
}
return true;
}
public boolean onKeyMultiple(int keyCode,int repeatCount,KeyEvent event){
return true;
}
// public class GameThread implements Runnable{//创建更新线程
// @Override
// public void run() {
// while(!Thread.currentThread().isInterrupted()){
// Message message = new Message();
// message.what = Game.REFRESH;
// Game.this.handler.sendMessage(message);//发送消息
// try{
// Thread.sleep(100);
// }catch(InterruptedException e){
// Thread.currentThread().interrupt();
// }
// }
// }
// }
public class GameThread implements Runnable{
public void run(){
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(100);
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
gameView.postInvalidate();//使用PostInvalidate可以直接在线程中更新界面 不需要Handler来传递消息
}
}
}
}
public class GameView extends View{
public int count = 0;
public int y =0;
public GameView(Context context) {
super(context);
}
public void onDraw(Canvas canvas){
if(count<8){
count++;
}else{
count=0;
}
Paint paint= new Paint();
switch(count%4){
case 0:
paint.setColor(Color.BLACK);
break;
case 1:
paint.setColor(Color.RED);
break;
case 2:
paint.setColor(Color.YELLOW);
break;
case 3:
paint.setColor(Color.GREEN);
break;
}
canvas.drawRect(y,y,y+40,y+40, paint);//绘制矩形
}
}
第二部分 SurfaceView类
1,开发复杂游戏,而且对程序的执行效率要求更高时用此类,因本身就是双缓冲机制的。
2,SurfaceView可以直接访问一个画布。
3,SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用而使用的。
4,View即其子类(如TextVie,Button)要画在Surface上。每个Surface创建一个Canvas对象(属性时常改变)用来管理View在Surface上绘制操作。
5,使用SurfaceView绘图时,一般都是出现在最顶层。在使用时要对其创建,销毁,情况改变进行监视,这就需要实现SurfaceHolder.Callback接口,
如果要对被绘制的画布进行裁剪,控制其大小时都需要使用SurfaceHolder来完成处理。
6,在程序中,SurfaceHolder对象需要通过getHolder方法来获得,同时还需要addCallback方法来添加“回调函数”。
7,SurfaceView与View不同之处,在于SurfaceView不需要通过线程来更新视图,在绘制前必须使用lockCanvas方法锁定画布,并得到画布,然后在画布上绘制
绘制完成后,使用unlockCanvasAndPost方法来解锁画布。
8,addCallback:给SurfaceView添加一个回调函数;
removeCallback:从SurfaceView移除回调函数;
public class GameView2 extends SurfaceView implements SurfaceHolder.Callback, Runnable{
SurfaceHolder surfaceHolder = null;//定义对象
public boolean loop = false;
public int count = 1;
public GameView2(Context context) {
super(context);
surfaceHolder = this.getHolder();//实例化SurfaceHolder对象
surfaceHolder.addCallback(this);//添加回调函数
loop = true;
this.setFocusable(true);//不知道这句什么意思?
}
private void Draw() {
Canvas canvas = surfaceHolder.lockCanvas();//锁定画布
if(surfaceHolder ==null||canvas ==null){
return ;
}
if(count<100){
count++;
}else{
count= 0;
}
Paint paint = new Paint();//创建画笔
paint.setAntiAlias(true);//设置抗锯齿
paint.setColor(Color.GREEN);//设置画笔颜色
canvas.drawRect(0,0,320,480, paint);//绘制矩形
switch(count%4){
case 0:
paint.setColor(Color.BLUE);
break;
case 1:
paint.setColor(Color.YELLOW);
break;
case 2:
paint.setColor(Color.RED);
break;
case 3:
paint.setColor(Color.CYAN);
break;
default:
paint.setColor(Color.WHITE);
break;
}
canvas.drawCircle(130, 240, 500, paint);
surfaceHolder.unlockCanvasAndPost(canvas);//绘制后解锁,绘制后必须解锁才能显示
}
@Override//在Surface大小发生改变时激发
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
// TODO Auto-generated method stub
}
@Override//在Surface创建时激发
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
new Thread(this).start();
}
@Override//在Surface销毁时调用
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
loop=false;
}
public void run(){
while(loop){
try{
Thread.sleep(200);
synchronized (surfaceHolder) {
Draw();
}
}catch(Exception e){
}
}
}
}
第三部分 Paint类和Color类
在Android中通过graphic类来显示2D图形。graphic包括Canvas类(画布),Paint(画笔),Color(颜色),Bitmap(图像),2D几何图形等常用类。
package graphics;
Paint类常用的一些方法:
setAntiAlias 设置画笔的锯齿效果
setColor 设置画笔颜色
setARGB 设置画笔的a,r,g,b值。
setAlpha 设置透明度
setTextSize 设置字体大小
setStyle 设置画笔风格 空心或实心
setStrokeWidth设置空心的边框宽度
getColor 得到画笔的颜色
getAlpha 得到画笔的透明度
Color类中定义了一些颜色常量
Color.rgb方法将整型的颜色转换成Color类型。如Color.red方法可提取出红色的值。
/**
* 注意:在绘制时按从上到下的顺序绘制的 ,后面的如果和前面的重合的话会将前面的覆盖掉
*/
public class Graphics_Paint_Color extends View implements Runnable {
public Paint paint;
private String TAG="你好";
public Graphics_Paint_Color(Context context) {
super(context);
paint = new Paint();//创建画笔
new Thread(this).start();//开启线程
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
paint.setAntiAlias(true);//设置抗锯齿
paint.setColor(Color.RED);//设置画笔颜色
paint.setColor(Color.rgb(12, 234, 34));//设置画笔颜色 其中参数分别为red,green,blue三种颜色的值
Color.red(23);//提取颜色?
Color.blue(234);
paint.setARGB(255, 23,234,49);//设置Paint的颜色和Alpha值
paint.setAlpha(200);//设置透明度
//paint.set(new PAINT());//设置另外一个Paint对象
paint.setTextSize(14);//设置字体的尺寸
//得到Paint的一些属性
Log.i(TAG, "paint的颜色"+paint.getColor());
Log.i(TAG,"paint的Alpha"+paint.getAlpha());
paint.setStyle(Paint.Style.STROKE);//设置Paint为空心
paint.setStrokeWidth(5);//设置空心的外框宽度
canvas.drawRect(0,0,50,60, paint);//绘制空心矩形
paint.setStyle(Paint.Style.FILL);//设置Paint为实心
canvas.drawRect(60,70,120,140, paint);//绘制实心的矩形
}
public void run() {
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(200);
}catch(Exception e){
e.printStackTrace();
Thread.currentThread().interrupt();
}
postInvalidate();//使用postInvalidate可以直接在线程中更新界面
}
}
}在View或SurfaceView中绘制图形后,需要在Activtiy中通过setContentView()来让其显示出来
第四部分 Canvas类和ShadeDrawable类
Canvas方法:
Canvas() 创建画布,可以用setBitmap()方法设置绘制具体画布
Canvas(Bitmap bitmap) 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,所以bitmap不得为null
Canvas(GL gl) 绘制3D效果时使用,与OpenGL
drawColor 设置画布背景色
setBitmap 设置具体画布
clipRect 设置显示区域,即设置裁剪区
isOpaque 检测是否支持透明
rotate 旋转画布 在游戏中我们需要对精灵旋转,缩放或其它操作就可以通过旋转画布来实现,但在旋转画布时会旋转画布上所以对象,而我们只需要旋转其中一个。这个时候
我们就需要用save方法来锁定需要操作的对象,在操作后通过restore方法来解锁。(例子有此方法的运用)
setViewport 设置画布中显示窗口
skew 设置便宜量
绘制几何图形的方法
drawRect 绘制矩形
drawCircle 绘制圆形
drawOval 绘制椭圆
drawLine 绘制直线
drawPoint 绘制点
public class CanvasView extends View implements Runnable{
private Paint paint ;
public ShapeDrawableView shapeDrawableView;
public CanvasView(Context context) {
super(context);
paint = new Paint();
shapeDrawableView= new ShapeDrawableView(context);//得到对象
new Thread(this).start();
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);//设置画布颜色
paint.setAntiAlias(true);//取消抗锯齿效果
//canvas.clipRect(20,20,80,80);//设置裁剪区域
canvas.save();//锁定画布
//canvas.rotate(45.0f);//旋转画布
paint.setColor(Color.RED);//设置颜色及绘制矩形
canvas.drawRect(new Rect(10,10,80,80), paint);
canvas.restore();//解锁画布
paint.setStyle(Paint.Style.STROKE);{//设置为空心画笔
Rect rect = new Rect();
rect.left =85;
rect.bottom=80;
rect.right=150;
rect.top = 10;
paint.setColor(Color.BLUE);
canvas.drawRect(rect, paint);//绘制矩形
canvas.drawCircle(60,60,50,paint);//绘制圆形
RectF rectF = new RectF();//绘制椭圆
rectF.bottom=80;
rectF.left=160;
rectF.right= 300;
rectF.top = 10;
canvas.drawOval(rectF, paint);
Path path = new Path();//绘制多边形
path.moveTo(10, 90);
path.lineTo(80, 100);
path.lineTo(70,150);
path.lineTo(10, 200);
path.close();//封闭多边形
canvas.drawPath(path, paint);
paint.setStrokeWidth(5);
canvas.drawLine(50,50,300,200, paint);
}
paint.setStyle(Paint.Style.FILL);{//设置画笔为实心
paint.setColor(Color.YELLOW);
canvas.drawRect(20,250,300,300, paint);
}
shapeDrawableView.DrawShape(canvas);//绘制另一View里的图形到本View中来
}
public void run() {
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(200);
}catch(Exception e){
Thread.currentThread().interrupt();
}
postInvalidate();//直接在线程中更新
}
}
}
/**
*Android中可以通过ShapeDrawable来绘制图像。可以通过getPaint方法得到Paint对象,
setBounds 此方法可设置图形显示的区域
通过ShapeDrawable的Draw方法将图形画到屏幕上
*/
public class ShapeDrawableView extends View{
ShapeDrawable shapeDrawable;
Paint paint;
public ShapeDrawableView(Context context) {
super(context);
}
public void DrawShape(Canvas canvas){
shapeDrawable = new ShapeDrawable();//------用ShapeDrawable绘图必须要实例化对象
paint=shapeDrawable.getPaint();//得到画笔
paint.setColor(Color.GREEN);//设置画笔颜色
Rect bounds =new Rect(10,310,80,390);//绘制矩形
shapeDrawable.setBounds(bounds);//------用ShapeDrawable绘图必须要使用SetBounds显示区域
shapeDrawable.draw(canvas);//--------用ShapeDrawable绘图必须要使用draw()使图像显示到屏幕上
//另一种写法
/* 实例化ShapeDrawable对象并说明是绘制一个椭圆 */
shapeDrawable = new ShapeDrawable(new OvalShape());
//得到画笔paint对象并设置其颜色
shapeDrawable.getPaint().setColor(Color.GREEN);
/* 设置图像显示的区域 */
shapeDrawable.setBounds(70, 250, 150, 280);
/* 绘制图像 */
shapeDrawable.draw(canvas);
//以下为绘制多边形
Path path1 = new Path();
/*设置多边形的点*/
path1.moveTo(150+5, 80+80-50);
path1.lineTo(150+45, 80+80-50);
path1.lineTo(150+30, 80+120-50);
path1.lineTo(150+20, 80+120-50);
/* 使这些点构成封闭的多边形 */
path1.close();
//PathShape后面两个参数分别是宽度和高度
shapeDrawable = new ShapeDrawable(new PathShape(path1,150,150));
//得到画笔paint对象并设置其颜色
shapeDrawable.getPaint().setColor(Color.BLUE);
/* 设置图像显示的区域 */
shapeDrawable.setBounds(100, 170, 200, 280);
/* 绘制图像 */
shapeDrawable.draw(canvas);
}
}
第五部分 字符串绘制
Android中提供了drawText方法来绘制字符串,在绘制执法串之前需要设置画笔对象,包括字符串的尺寸,颜色等属性。使用FontMetrics来规划字体属性
可以用getFontMetrics方法来获得系统字体的相关内容。
setTextSize 设置字符串的尺寸
setARGB 设置颜色
getTextWidths 取得字符串宽度
setFlags(Paint.ANTI_ALIAS_FLAG) 消除锯齿
measureText 得到字符串宽度
得到字符串高度可用:
FontMetrics fontMetrics = paint.getFontMetrics();
textselfHeight = (int)Math.ceil(fontMetrics.descent - fontMetrics.top)+2;//得到文字的高度
textPageLineNum = textHeight/textselfHeight;//每一页的行数 = 绘制区域的高度/文字本身的高度
下例实现了文本翻页,换行。
* 实现文字自动换行
* 自动翻页
public class TextUtil{
int m_iTextPosX; //绘制的x点
int m_iTextPosY; //绘制的y点
int m_iTextWidth; //绘制宽度
int m_iTextHeight; //绘制高度
int m_iFontHeight; //字体高度
int m_ipageLineNum; //每一页显示的行数
int m_iTextBGColor; // 背景颜色
int m_iTextColor; // 字体颜色
int m_iAlpha; //Alpha值
int m_iRealLine; // 字符串真实的行数
int m_iCurLine; //当前行
String m_strText;
Vector m_String;
Paint m_paint;
int m_iTextSize;
public TextUtil(){
m_paint = new Paint();
m_String = new Vector();
}
public TextUtil(String strText, int x, int y, int w, int h, int bgcolor, int txetcolor, int a, int iTextSize){
m_paint = new Paint();
m_String = new Vector();
m_strText = strText;
m_iTextPosX = x;
m_iTextPosY = y;
m_iTextWidth = w;
m_iTextHeight = h;
m_iTextBGColor = bgcolor;
m_iTextColor = txetcolor;
m_iTextSize = iTextSize;
m_iAlpha = a;
}
/**
* 初始化
* @param strText 要显示的字符串
* @param x x
* @param y y
* @param w w
* @param h h
* @param bgcolor 背景颜色
* @param txetcolor 文字的颜色
* @param a Alpha
* @param iTextSize 字体大小
*/
public void InitText(String strText, int x, int y, int w, int h, int bgcolor, int txetcolor, int a, int iTextSize){
m_iCurLine = 0;
m_ipageLineNum = 0;
m_iRealLine = 0;
m_strText = "";
m_iTextPosX = 0;
m_iTextPosY = 0;
m_iTextWidth = 0;
m_iTextHeight = 0;
m_iTextBGColor = 0;
m_iTextColor = 0;
m_iTextSize = 0;
m_iAlpha = 0;
m_String.clear();
SetText(strText);
SetRect(x, y, w, h);
SetBGColor(bgcolor);
SetTextColor(txetcolor);
SetFontSize(iTextSize);
SetAlpha(a);
SetPaint();
GetTextIfon();
}
/**
* 设置Alpha
* @param a Alpha值
*/
public void SetAlpha(int a){
m_iAlpha = a;
}
/**
* 对Paint属性的设置
*/
public void SetPaint(){
m_paint.setARGB(m_iAlpha, Color.red(m_iTextColor), Color.green(m_iTextColor), Color.blue(m_iTextColor));
m_paint.setTextSize(m_iTextSize);
}
/**
* 设置字体尺寸
* @param iTextSize
*/
public void SetFontSize(int iTextSize){
m_iTextSize = iTextSize;
}
/**
* 设置显示文本的区域
* @param x
* @param y
* @param w
* @param h
*/
public void SetRect(int x, int y, int w, int h){
m_iTextPosX = x;
m_iTextPosY = y;
m_iTextWidth = w;
m_iTextHeight = h;
}
/**
* 设置背景颜色
* @param bgcolor
*/
public void SetBGColor(int bgcolor){
m_iTextBGColor = bgcolor;
}
/**
* 设置字体颜色
* @param txetcolor
*/
public void SetTextColor(int txetcolor){
m_iTextColor = txetcolor;
}
/**
* 色绘制要显示的字符串
* @param strText
*/
public void SetText(String strText){
m_strText = strText;
}
/**
* 得到字符串的信息
* 包括:行数、页数等信息
* 内部调用
*/
public void GetTextIfon(){
char ch;
int w = 0;
int istart = 0;
FontMetrics fm = m_paint.getFontMetrics();
m_iFontHeight = (int) Math.ceil(fm.descent - fm.top) + 2;
m_ipageLineNum = m_iTextHeight / m_iFontHeight;
for (int i = 0; i < m_strText.length(); i++){
ch = m_strText.charAt(i);
float[] widths = new float[1];
String srt = String.valueOf(ch);
m_paint.getTextWidths(srt, widths);
if (ch == '\n'){
m_iRealLine++;
m_String.addElement(m_strText.substring(istart, i));
istart = i + 1;
w = 0;
}else{
w += (int) (Math.ceil(widths[0]));
if (w > m_iTextWidth){
m_iRealLine++;
m_String.addElement(m_strText.substring(istart, i));
istart = i;
i--;
w = 0;
}else{
if (i == (m_strText.length() - 1)){
m_iRealLine++;
m_String.addElement(m_strText.substring(istart, m_strText.length()));
}
}
}
}
}
/**
* 绘制字符串
* @param canvas
*/
public void DrawText(Canvas canvas){
for (int i = m_iCurLine, j = 0; i < m_iRealLine; i++, j++){
if (j > m_ipageLineNum){
break;
}
canvas.drawText((String) (m_String.elementAt(i)), m_iTextPosX, m_iTextPosY + m_iFontHeight * j, m_paint);
}
}
/**
* 翻页等按键处理
* @param keyCode
* @param event
* @return
*/
public boolean KeyDown(int keyCode, KeyEvent event){
if (keyCode == KeyEvent.KEYCODE_DPAD_UP){
if (m_iCurLine > 0){
m_iCurLine--;
}
}else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
if ((m_iCurLine + m_ipageLineNum) < (m_iRealLine - 1)){
m_iCurLine++;
}
}
return false;
}
}
public class GameView extends View implements Runnable{
/* 声明Paint对象 */
private Paint mPaint = null;
private int mICount = 0;
/* 声明TextUtil对象 */
private TextUtil mTextUtil = null;
public GameView(Context context){
super(context);
/* 构建对象 */
mPaint = new Paint();
String string = "测试自动换行\n\n设置文字自动换行abcdefgh\niklmnopqrst换行123347465\n43756245Android\n设置文字自动换行abcdefgh\n
iklmnopqrst换行123347465\n43756245Android";
/* 实例化TextUtil */
mTextUtil = new TextUtil(string,5,50,300,80,0x0,0xffffff,255,16);
/* 初始化TextUtil */
mTextUtil.InitText(string,5,150,300,80,0x0,0xffffff,255,16);
/* 开启线程 */
new Thread(this).start();
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
/* 设置背景颜色 */
canvas.drawColor(Color.BLACK);
mPaint.setAntiAlias(true);
if ( mICount < 100 ){
mICount++;
}
mPaint.setColor(Color.RED);
canvas.drawText("装在进度:"+mICount+"%......", 10, 20, mPaint);
/* 绘制TextUtil:实现自动换行 */
mTextUtil.DrawText(canvas);
}
// 触笔事件
public boolean onTouchEvent(MotionEvent event){
return true;
}
// 按键按下事件
public boolean onKeyDown(int keyCode, KeyEvent event){
return mTextUtil.KeyDown(keyCode, event);
}
// 按键弹起事件
public boolean onKeyUp(int keyCode, KeyEvent event){
return false;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event){
return true;
}
/**
* 线程处理
*/
public void run(){
while (!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(100);
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
//使用postInvalidate可以直接在线程中更新界面
postInvalidate();
}
}
}
public class Activity01 extends Activity{
private GameView mGameView = null;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mGameView = new GameView(this);
setContentView(mGameView);
}
// 按键弹起事件
public boolean onKeyUp(int keyCode, KeyEvent event){
return super.onKeyUp(keyCode, event);
}
// 按键按下事件
public boolean onKeyDown(int keyCode, KeyEvent event){
mGameView.onKeyDown(keyCode, event);
return true;
}
}
第六部分 图像的绘制 旋转 缩放
1.((BitmapDrawable) getResource().getDrawable(资源索引)).getBitmap() 加载资源
2.canvas.drawBitmap(bitmap,x,y,null);将bitmap绘制在屏幕的(x,y)位置
3.getHeight() ,getWidth() 获得图片的高度好宽度
4.Android中通过Matrix旋转图片。Matrix没有结果体,它必须被实例化,可通过reset方法或set方法来实现。
setRotate方法来设置旋转角度
createBitmap 创建一个经过旋转等处理的Bitmap对象
5.Matrix的postScale(x,y)方法来设置缩放的倍数 分别在x,y方法缩放的倍数。
public class Draw_Bitmap extends Activity{
public DrawBitmap drawBitmap;
public void onCreate(Bundle savedInstanceState){
drawBitmap = new DrawBitmap(this);
super.onCreate(savedInstanceState);
setContentView(drawBitmap);
}
public boolean onKeyDown(int keyCode,KeyEvent event){
if(drawBitmap == null){
return false;
}else if(keyCode ==KeyEvent.KEYCODE_BACK){
this.finish();
return true;
}
return drawBitmap.onKeyDown(keyCode, event);
}
}
public class DrawBitmap extends View implements Runnable{
private static Paint paint1=null;
public static Bitmap dog1=null;
public Bitmap tb1=null;
public int dog1width = 0;
public int dog1height = 0;
public float rotate=0.0f;
public float scale = 1.0f;
Matrix matrix = new Matrix();//构建Matrix对象 Matrix用于旋转图片
public DrawBitmap(Context context) {
super(context);
paint1 = new Paint();
//从资源中装载图片
dog1 = ((BitmapDrawable)getResources().getDrawable(R.drawable.dog)).getBitmap();
tb1 = ((BitmapDrawable)getResources().getDrawable(R.drawable.tb)).getBitmap();
dog1width = dog1.getWidth();//得到图像的宽度
dog1height =dog1.getHeight();//得到图像的高度
new Thread(this).start();//创建,开启线程
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
canvas.drawBitmap(dog1,10,240,paint1);
matrix.reset();//重置matrix得到Matrix的方法体
matrix.setRotate(rotate);//设置旋转
matrix.postScale(scale, scale);//设置缩放分别为(x,y)方法的缩放
Bitmap dog1rotate = Bitmap.createBitmap(dog1,0,0,dog1width,dog1height,matrix,true);//按Matrix的旋转构建新的Bitmap
DrawBitmap.drawImage(canvas,dog1rotate,130,240);//绘制旋转后的图片
dog1rotate= null;//这里难道是释放资源
DrawBitmap.drawImage(canvas,dog1,0,0);//在屏幕的(0,0)处绘制图片
DrawBitmap.drawImage2(canvas,tb1,10,dog1.getHeight(),142,100,150,178);//在指定位置按指定的裁决区域进行绘制
}
/**
* i---屏幕上的x坐标
* height---屏幕上的y坐标
* j---要绘制图片的宽度
*k---要绘制图片的高度
*l---图片上的x坐标
*m---图片上的Y坐标
*/
private static void drawImage2(Canvas canvas, Bitmap tb12, int i,
int height, int j, int k, int l, int m) {
Rect rect1 = new Rect();//图片上裁剪区域
rect1.left = l;
rect1.top =m;
rect1.right=l+j;
rect1.bottom = m+k;
Rect rect2 = new Rect();//屏幕上的裁剪区域
rect2.left = i;
rect2.top = height;
rect2.right =i+j;
rect2.bottom = height+k;
canvas.drawBitmap(tb12, rect1,rect2, null);
}
private static void drawImage(Canvas canvas, Bitmap dog12, int i, int j){
canvas.drawBitmap(dog12,i,j,null);
}
public boolean onKeyDown(int keyCode,KeyEvent event){
if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT){
rotate+=10;
}else if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){
rotate--;
}else if(keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
if(scale>0.3){//注意:scale必须大于0,否则会报错
scale-=0.1;
}
}else if(keyCode == KeyEvent.KEYCODE_DPAD_UP){
if(scale<2.0){
scale+=0.1;
}
}
return true;
}
public boolean onKeyUp(int keyCode,KeyEvent event){
return false;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event){
return true;
}
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(100);
}catch(Exception e){
Thread.currentThread().interrupt();
}
postInvalidate();
}
}
}
第七部分 图像像素的操作
Android中Bitmap提供了操作像素的方法,可通过getPixels方法获得图像像素并放到一个数组中去。我们处理这个数组就可以了,最后通过setPixels设置这个像素数组到Bitmap中去。
Android中,每一个图像像素通过4字节整数来展现:最高位字节用作Alpha通道。即用来实现透明与不透明控制,255代表完全透明,0为完全不透明;接下来的一个字节是Red红色通道。
255代表完全是红色。依次类推,接下来的两个字节相应地实现绿色和蓝色通道。
下例为通过图像像素的操作来模拟水纹效果。
public class GameView extends View implements Runnable{
int BACKWIDTH;
int BACKHEIGHT;
short[] buf2;
short[] buf1;
int[] Bitmap2;
int[] Bitmap1;
public GameView(Context context){
super(context);
Bitmap image = BitmapFactory.decodeResource(this.getResources(),R.drawable.qq);//装载图片
BACKWIDTH = image.getWidth();
BACKHEIGHT = image.getHeight();
buf2 = new short[BACKWIDTH * BACKHEIGHT];
buf1 = new short[BACKWIDTH * BACKHEIGHT];
Bitmap2 = new int[BACKWIDTH * BACKHEIGHT];
Bitmap1 = new int[BACKWIDTH * BACKHEIGHT];
image.getPixels(Bitmap1, 0, BACKWIDTH, 0, 0, BACKWIDTH, BACKHEIGHT);//加载图片的像素到数组中
new Thread(this).start();
}
public void DropStone(
int x, // x坐标
int y, // y坐标
int stonesize, // 波源半径
int stoneweight){ // 波源能量
for (int posx = x - stonesize; posx < x + stonesize; posx++)
for (int posy = y - stonesize; posy < y + stonesize; posy++)
if ((posx - x) * (posx - x) + (posy - y) * (posy - y) < stonesize * stonesize)
buf1[BACKWIDTH * posy + posx] = (short) -stoneweight;
}
public void RippleSpread(){
for (int i = BACKWIDTH; i < BACKWIDTH * BACKHEIGHT - BACKWIDTH; i++){
buf2[i] = (short) (((buf1[i - 1] + buf1[i + 1] + buf1[i - BACKWIDTH] + buf1[i + BACKWIDTH]) >> 1) - buf2[i]);// 波能扩散
buf2[i] -= buf2[i] >> 5;// 波能衰减
}
// 交换波能数据缓冲区
short[] ptmp = buf1;
buf1 = buf2;
buf2 = ptmp;
}
public void render(){//渲染你水纹效果
int xoff, yoff;
int k = BACKWIDTH;
for (int i = 1; i < BACKHEIGHT - 1; i++){
for (int j = 0; j < BACKWIDTH; j++){
// 计算偏移量
xoff = buf1[k - 1] - buf1[k + 1];
yoff = buf1[k - BACKWIDTH] - buf1[k + BACKWIDTH];
// 判断坐标是否在窗口范围内
if ((i + yoff) < 0){
k++;
continue;
}
if ((i + yoff) > BACKHEIGHT){
k++;
continue;
}
if ((j + xoff) < 0){
k++;
continue;
}
if ((j + xoff) > BACKWIDTH){
k++;
continue;
}
// 计算出偏移象素和原始象素的内存地址偏移量
int pos1, pos2;
pos1 = BACKWIDTH * (i + yoff) + (j + xoff);
pos2 = BACKWIDTH * i + j;
Bitmap2[pos2++] = Bitmap1[pos1++];
k++;
}
}
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawBitmap(Bitmap2, 0, BACKWIDTH, 0, 0, BACKWIDTH, BACKHEIGHT, false, null);//绘制经过处理的图片效果
}
public boolean onTouchEvent(MotionEvent event){// 触笔事件
return true;
}
public boolean onKeyDown(int keyCode, KeyEvent event){// 按键按下事件
return true;
}
public boolean onKeyUp(int keyCode, KeyEvent event){// 按键弹起事件
DropStone(BACKWIDTH/2, BACKHEIGHT/2, 10, 30);
return false;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event){
return true;
}
public void run(){//线程处理
while (!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(50);
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
RippleSpread();
render();
//使用postInvalidate可以直接在线程中更新界面
postInvalidate();
}
}
}
第八部分 Shader类 图片的渲染
/**
* 所谓的渲染就是对画笔的设置 本人是这样理解的。Shader类的使用,都需要先构建Shader对象,然后通过Paint的setShader方法来设置渲染对象,然后在绘制时使用Paint
对象。当然用不同的渲染需要构建不同的对象(即相当于创建不同的画笔)。
*/
public class Shader01 extends View{
Bitmap qq2 = null;
int qq2width = 0;
int qq2height = 0;
Paint paint = null;
Shader bitmapShaper = null;//Bitmap渲染
Shader linearGradient = null;//线性渲染
Shader radialGradient = null;//环形渲染
Shader sweepGradient = null;//梯度渲染
Shader composeShaper = null;//混合渲染
ShapeDrawable shapeDrawable = null;
public Shader01(Context context) {
super(context);
qq2 = ((BitmapDrawable) getResources().getDrawable(R.drawable.qq)).getBitmap();//装载资源
qq2width = qq2.getWidth();//得到图片的宽度
qq2height = qq2.getHeight();//得到图片的高度
//创建BitmapShader对象
bitmapShaper = new BitmapShader(qq2,Shader.TileMode.REPEAT,Shader.TileMode.MIRROR);//要想裁剪需要使用BitmapShader类来裁剪,得到图片的宽度和高度
//创建linearGradient对象并设置变化的颜色数组
linearGradient = new LinearGradient(10, 0, 50, 100,
new int []{Color.RED,Color.YELLOW,Color.BLACK}, null, Shader.TileMode.MIRROR);
//混合渲染
composeShaper =new ComposeShader(linearGradient, bitmapShaper, PorterDuff.Mode.DARKEN);//也可选用其他的混合
//环形渲染
radialGradient = new RadialGradient(50, 200, 50,
new int[]{Color.GREEN,Color.RED,Color.BLUE}, null, Shader.TileMode.REPEAT);
//梯度渲染
sweepGradient = new SweepGradient(30, 30, new int[]{Color.GREEN,Color.RED,Color.BLUE}, null);
paint = new Paint();//创建画笔
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
shapeDrawable = new ShapeDrawable(new OvalShape());//设置绘制椭圆
shapeDrawable.getPaint().setShader(bitmapShaper);//设置要绘制的椭圆形的图片
shapeDrawable.setBounds(0,0,qq2width,qq2height);//设置要显示的区域
shapeDrawable.draw(canvas);//绘制图片
paint.setShader(linearGradient);//绘制线性渲染
canvas.drawRect(200,0,320,50, paint);
paint.setShader(composeShaper);//绘制混合渲染
canvas.drawRect(200,55,320,105, paint);
paint.setShader(radialGradient);//绘制环形渲染
canvas.drawRect(200,200,320,250, paint);
paint.setShader(sweepGradient);//绘制梯度渲染
canvas.drawRect(200,110,320,160, paint);
}
}
=================================================================================================================================================
第九部分 双缓冲技术
原理:当一个动画争先显示时,程序又在改变它,前面的还没画完,程序又请求重新绘制,这时就会出现闪屏。为避免这种情况,可以使用双缓冲技术,将要处理好的的图片
在内存中处理好之后,再将其显示到屏幕上。Android中SurfaceView类本身就是一个双缓冲机制,所以游戏开发中用SurfaceView用得更多,效率也更高。下例是针对View类实现
双缓冲技术的例子。
public class BufferView extends View implements Runnable{-----------???
Bitmap bitmap1= null;
Bitmap bufferBitmap = null;//创建缓冲区
Canvas canvas1 = null;
Paint paint = null;
public BufferView(Context context) {
super(context);
bitmap1 = ((BitmapDrawable)getResources().getDrawable(R.drawable.qq)).getBitmap();//装载资源
bufferBitmap = Bitmap.createBitmap(320,480,Config.ARGB_8888);//创建屏幕大小的缓冲区
canvas1 = new Canvas();//创建画布(程序中出现了两块画布)
paint = new Paint();
canvas1.setBitmap(bufferBitmap);//将内容绘制在bufferBitmap缓冲区上,可理解为将缓冲区作为画布。
canvas1.drawBitmap(bitmap1, 0,0, paint);//将图片绘制到缓冲区上
new Thread(this).start();
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawBitmap(bufferBitmap, 0,0, paint);//将bufferBitmap缓冲区上画布绘制到屏幕上去
}
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(200);
}catch(Exception e){
Thread.currentThread().interrupt();
}
postInvalidate();
}
}
}
=================================================================================================================================================
第十部分 全屏显示及获取屏幕属性
如果想像G1游戏那样,当我们把手机屏幕横放时,应用程序会自动变为横屏模式。其实可以通过下面两个方法实现这个功能。
法一:双击AndroidManifest.xml文件,选择Application选项卡,选中Activity类,再找到Screen orientation选项 ,选择 senor 最后保存即可。在真机上就能看到效果
法二:在AndroidManifest.xml文件中设计如下中加入android:screenOrientation="senor"即可。如下:
<activity android:name=".GameActivity"
android:label="@string/app_name"
android:screenOrientation="senor">
public class FullScreen extends Activity{
public TextView textView_FullScreen;//定义一个TextView来显示屏幕属性
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//通过requestWindowFeature方法来设置为无标题栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);//通过setFlags方法设置为全屏模式
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//通过setRequestedOrientation方法来设置为横屏
setContentView(R.layout.fullscreen);
textView_FullScreen = (TextView)this.findViewById(R.id.textView_FullScreen);//得到TextView对象
DisplayMetrics displayMetrics = new DisplayMetrics();//定义DisplayMetrics对象,DisplayMetrics定义了屏幕的一些属性
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);//通过getMetrics方法来取得窗口属性
int screenWidth = displayMetrics.widthPixels;//得到窗口宽度
int screenHeight = displayMetrics.heightPixels;//得到窗口高度
textView_FullScreen.setText("屏幕宽度:"+screenWidth+"\n屏幕高度:"+screenHeight);
}
}
第十一部分 Tween动画
第一种方法 在代码中设计
public class AnimationView extends View{
public Animation animationAlpha = null;
public Animation animationScale = null;
public Animation animationTranslate = null;
public Animation animationRotate = null;
Bitmap bitmap1 = null;
public AnimationView(Context context) {
super(context);
bitmap1 = ((BitmapDrawable)getResources().getDrawable(R.drawable.dog)).getBitmap();//加载资源
}
public void onDraw(Canvas canvas){
canvas.drawBitmap(bitmap1, 50,100, null);
}
public boolean onKeyUp(int keyCode,KeyEvent event){
switch(keyCode){
/*
* Alpha透明度动画效果。其属性设置格式为:
* AlphaAnimation(float fromAlpha,float toAlpha)
* fromAlpha---为动画起始时透明度
* toAlpha---动画结束时透明度(0.0表示完全透明,1.0表示完全不透明)
*
* setDuration(long durationMillis);设置动画显示的时间 单位为毫秒
* startAnimation(Animation animation);开始播放动画
*/
case KeyEvent.KEYCODE_DPAD_UP:
animationAlpha = new AlphaAnimation(0.0f, 1.0f);//创建Alpha动画
animationAlpha.setDuration(3000);//动画持续时间
this.startAnimation(animationAlpha);//开始播放动画
break;
/*
* Scale伸缩动画效果.。其属性设置如下:
* ScaleAnimation(fromX, toX, fromY, toY, pivotXType, pivotXValue, pivotYType, pivotYValue)
* fromX,toX 为起始和结束时x坐标上的伸缩尺寸
* fromY,toY 为起始和结束时y坐标上的伸缩尺寸
* pivotXType,pivotYType 分别为x,y的伸缩模式
* pivotXVale,pivotYVale 分别为伸缩动画相对于x,y的坐标开始位置
*/
case KeyEvent.KEYCODE_DPAD_DOWN:
animationScale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);//创建Scale动画
animationScale.setDuration(500);//动画持续的时间
this.startAnimation(animationScale);//开始播放动画
/*
* Translate移动动画效果。其属性设置如下:
* TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta)
* fromXDelta ,fromYDelta 分别为起始x,y坐标
* toXDelta,toYDelta 分别为终点时x,y坐标
*/
case KeyEvent.KEYCODE_DPAD_LEFT:
animationTranslate = new TranslateAnimation(10, 100, 10, 100);//创建Translate动画
animationTranslate.setDuration(1000);//动画持续时间
this.startAnimation(animationTranslate);//开始播放动画
break;
/*
* Rotate旋转动画效果
* RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue)
* fromDegrees 开始角度
* toDegrees 结束角度
* pivotXValue , pivotYValue 伸缩动画相对于x,y的坐标坐标开始位置
* pivotXType,pivotYType 分别为x,y的伸缩模式
*/
case KeyEvent.KEYCODE_DPAD_RIGHT:
animationRotate = new RotateAnimation(0.0f, 360f, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF, 0.5f);//创建Rotate动画
animationRotate.setDuration(1000);//动画持续时间
this.startAnimation(animationRotate);//动画播放
}
return true;
}
} 需在Activity种实例化
-------------------------------------------------------------------------------------------------------
第二种方法 xml设计
aipha.xml
<?xml version="1.0" encoding="utf-8"?>//也可将这四个xml放在一起 为没一个设置一个id
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<alpha
android:fromAlpha="0.1"
android:toAlpha="1.0"
android:duration="2000"
/>
</set>
rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0"
android:toDegrees="+360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000" />
</set>
scale.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="0.0"
android:toXScale="1.0"
android:fromYScale="0.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="500" />
</set>
translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="10"
android:toXDelta="100"
android:fromYDelta="10"
android:toYDelta="100"
android:duration="1000"
/>
</set>
public class GameView extends View{
private Animation mAnimationAlpha = null;//定义Alpha动画
private Animation mAnimationScale = null;//定义Scale动画
private Animation mAnimationTranslate = null;//定义Translate动画
private Animation mAnimationRotate = null;//定义Rotate动画
Bitmap mBitQQ = null;//定义Bitmap对象
Context mContext = null;
public GameView(Context context){
super(context);
mContext = context;
mBitQQ = ((BitmapDrawable) getResources().getDrawable(R.drawable.qq)).getBitmap();//装载资源
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawBitmap(mBitQQ, 0, 0, null);//绘制图片
}
public boolean onKeyUp(int keyCode, KeyEvent event){
switch ( keyCode ){
case KeyEvent.KEYCODE_DPAD_UP:
mAnimationAlpha = AnimationUtils.loadAnimation(mContext,R.layout.alpha);//装载动画布局
this.startAnimation(mAnimationAlpha);//开始播放动画
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
mAnimationScale = AnimationUtils.loadAnimation(mContext,R.layout.scale);//装载动画布局
this.startAnimation(mAnimationScale);//开始播放动画
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
mAnimationTranslate = AnimationUtils.loadAnimation(mContext,R.layout.translate);//装载动画布局
this.startAnimation(mAnimationTranslate);//开始播放动画
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
mAnimationRotate = AnimationUtils.loadAnimation(mContext,R.layout.rotate);//装载动画布局
this.startAnimation(mAnimationRotate);//开始播放动画
break;
}
return true;
}
}
===================================================================================================================================
Framed动画
第一种方法 在代码中设计
public class GameView extends View{
private AnimationDrawable animationDrawable = null;//定义AnimationDrawable装载动画
Context mContext = null;
Drawable mBitAnimation = null;//定义一个Drawable对象
public GameView(Context context){
super(context);
mContext = context;
animationDrawable = new AnimationDrawable();//实例化AnimationDrawable对象
for (int i = 1; i <= 15; i++){//利用循环装载所有名字类似的资源(图片命名时是有规律的 所以才能用此循环)
int id = getResources().getIdentifier("a" + i, "drawable", mContext.getPackageName());------------------------需要深入
mBitAnimation = getResources().getDrawable(id);
animationDrawable.addFrame(mBitAnimation, 500);//通过addFrame方法把每一帧添加进去,每一帧显示的时间为500毫秒
}
animationDrawable.setOneShot( false );//利用setOneShot方法设置播放模式是否循环false表示循环而true表示不循环
this.setBackgroundDrawable(animationDrawable);// 加载本类将要显示这个动画
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
}
public boolean onKeyUp(int keyCode, KeyEvent event){
switch ( keyCode ){
case KeyEvent.KEYCODE_DPAD_UP:
animationDrawable.start();//最后通过start方法播放动画
break;
}
return true;
}
} 要想显示Frame动画,还要在Activity中添加GameView对象
------------------------------------------------------------------------------
第二种方法:在XML文件中的设计
frameanimation.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/a1" android:duration="500" />
<item android:drawable="@drawable/a2" android:duration="500" />
<item android:drawable="@drawable/a3" android:duration="500" />
<item android:drawable="@drawable/a4" android:duration="500" />
<item android:drawable="@drawable/a5" android:duration="500" />
<item android:drawable="@drawable/a6" android:duration="500" />
<item android:drawable="@drawable/a7" android:duration="500" />
<item android:drawable="@drawable/a8" android:duration="500" />
<item android:drawable="@drawable/a9" android:duration="500" />
<item android:drawable="@drawable/a10" android:duration="500" />
<item android:drawable="@drawable/a11" android:duration="500" />
<item android:drawable="@drawable/a12" android:duration="500" />
</animation-list>
public class GameView extends View{
private AnimationDrawable frameAnimation = null;
Context mContext = null;
public GameView(Context context){
super(context);
mContext = context;
ImageView img = new ImageView(mContext);//定义一个ImageView用来显示动画
img.setBackgroundResource(R.anim.frameanimation);//装载动画布局文件
frameAnimation = (AnimationDrawable) img.getBackground();//构建动画
frameAnimation.setOneShot( false ); //设置是否循环
this.setBackgroundDrawable(frameAnimation);//设置该类显示的动画
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
}
public boolean onKeyUp(int keyCode, KeyEvent event){
switch ( keyCode ){
case KeyEvent.KEYCODE_DPAD_UP:
frameAnimation.start();//开始播放动画
break;
}
return true;
}
} 还许要在Acitivity类中实例化GameView对象才能显示。
===================================================================================================================================================
GIF动画播放
其原理:首先需要对GIF动画图像进行解码,然后将GIF中的每一帧分别提取出来保存到一个容器中,然后根据需要绘制每一帧,这样就可以轻松地实现GIF动画的播放了。
下面有个例子,可以在工作中直接使用。
public class GifDecoder{
private int E0;
private int E1[];
private int E2;
private int E6;
private boolean E7;
private int E8[];
private int width;
private int height;
private int ED;
private boolean EE;
private boolean EF;
private int F0[];
private int F1;
private boolean F2;
private int F3;
private long F4;
private int F5;
private static final int F6[] = { 8, 8, 4, 2 };
private static final int F8[] = { 0, 4, 2, 1 };
int curFrame;
int poolsize;
int FA;
byte C2[];
int FB;
int FC;
int FD;
public GifDecoder(byte abyte0[]){
E0 = -1;
E1 = new int[280];
E2 = -1;
E6 = 0;
E7 = false;
E8 = null;
width = 0;
height = 0;
ED = 0;
EE = false;
EF = false;
F0 = null;
F1 = 0;
F5 = 0;
curFrame = 0;
C2 = abyte0;
poolsize = C2.length;
FA = 0;
}
public boolean moreFrames(){
return poolsize - FA >= 16;
}
public void nextFrame(){
curFrame++;
}
public Bitmap decodeImage(){
return decodeImage(curFrame);
}
public Bitmap decodeImage(int i){
if (i <= E0){
return null;
}
if (E0 < 0){
if (!E3()){
return null;
}
if (!E4()){
return null;
}
}
do{
if (!E9(1)){
return null;
}
int j = E1[0];
if (j == 59){
return null;
}
if (j == 33){
if (!E7()){
return null;
}
}
else if (j == 44){
if (!E5()){
return null;
}
Bitmap image = createImage();
E0++;
if (E0 < i){
image = null;
}else{
return image;
}
}
}
while (true);
}
public void clear(){
C2 = null;
E1 = null;
E8 = null;
F0 = null;
}
private Bitmap createImage(){
int i = width;
int j = height;
int j1 = 0;
int k1 = 0;
int ai[] = new int[4096];
int ai1[] = new int[4096];
int ai2[] = new int[8192];
if (!E9(1)){
return null;
}
int k = E1[0];
int[] image = new int[width * height];
int ai3[] = E8;
if (EE){
ai3 = F0;
}
if (E2 >= 0){
ai3[E2] = 0xffffff;
}
int l2 = 1 << k;
int j3 = l2 + 1;
int k2 = k + 1;
int l3 = l2 + 2;
int k3 = -1;
int j4 = -1;
for (int l1 = 0; l1 < l2; l1++){
ai1[l1] = l1;
}
int j2 = 0;
E2();
j1 = 0;
label0: for (int i2 = 0; i2 < j; i2++){
int i1 = 0;
do{
if (i1 >= i){
break;
}
if (j2 == 0){
int i4 = E1(k2);
if (i4 < 0){
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
if (i4 > l3 || i4 == j3){
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
if (i4 == l2){
k2 = k + 1;
l3 = l2 + 2;
k3 = -1;
continue;
}
if (k3 == -1){
ai2[j2++] = ai1[i4];
k3 = i4;
j4 = i4;
continue;
}
int i3 = i4;
if (i4 == l3){
ai2[j2++] = j4;
i4 = k3;
}
for (; i4 > l2; i4 = ai[i4]){
ai2[j2++] = ai1[i4];
}
j4 = ai1[i4];
if (l3 >= 4096){
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
ai2[j2++] = j4;
ai[l3] = k3;
ai1[l3] = j4;
if (++l3 >= 1 << k2 && l3 < 4096){
k2++;
}
k3 = i3;
}
int l = ai2[--j2];
if (l < 0){
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
if (i1 == 0){
FC = 0;
FB = ai3[l];
FD = 0;
}else if (FB != ai3[l]){
for (int mm = FD; mm <= FD + FC; mm++){
image[j1 * width + mm] = FB;
}
FC = 0;
FB = ai3[l];
FD = i1;
if (i1 == i - 1){
image[j1 * width + i1] = ai3[l];
}
}else{
FC++;
if (i1 == i - 1){
for (int mm = FD; mm <= FD + FC; mm++){
image[j1 * width + mm] = FB;
}
}
}
i1++;
}
while (true);
if (EF){
j1 += F6[k1];
do{
if (j1 < j){
continue label0;
}
if (++k1 > 3){
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
j1 = F8[k1];
}
while (true);
}
j1++;
}
return Bitmap.createBitmap(image, width, height, Config.RGB_565);
}
private int E1(int i){
while (F5 < i){
if (F2){
return -1;
}
if (F1 == 0){
F1 = E8();
F3 = 0;
if (F1 <= 0){
F2 = true;
break;
}
}
F4 += E1[F3] << F5;
F3++;
F5 += 8;
F1--;
}
int j = (int) F4 & (1 << i) - 1;
F4 >>= i;
F5 -= i;
return j;
}
private void E2(){
F5 = 0;
F1 = 0;
F4 = 0L;
F2 = false;
F3 = -1;
}
private boolean E3(){
if (!E9(6)){
return false;
}
return E1[0] == 71 && E1[1] == 73 && E1[2] == 70 && E1[3] == 56 && (E1[4] == 55 || E1[4] == 57) && E1[5] == 97;
}
private boolean E4(){
if (!E9(7)){
return false;
}
int i = E1[4];
E6 = 2 << (i & 7);
E7 = EB(i, 128);
E8 = null;
return !E7 || E6(E6, true);
}
private boolean E5(){
if (!E9(9)){
return false;
}
width = EA(E1[4], E1[5]);
height = EA(E1[6], E1[7]);
int i = E1[8];
EE = EB(i, 128);
ED = 2 << (i & 7);
EF = EB(i, 64);
F0 = null;
return !EE || E6(ED, false);
}
private boolean E6(int i, boolean flag){
int ai[] = new int[i];
for (int j = 0; j < i; j++){
if (!E9(3)){
return false;
}
ai[j] = E1[0] << 16 | E1[1] << 8 | E1[2] | 0xff000000;
}
if (flag){
E8 = ai;
}else{
F0 = ai;
}
return true;
}
private boolean E7(){
if (!E9(1)){
return false;
}
int i = E1[0];
int j = -1;
switch (i){
case 249:
j = E8();
if (j < 0){
return true;
}
if ((E1[0] & 1) != 0){
E2 = E1[3];
}else{
E2 = -1;
}
break;
}
do{
j = E8();
}
while (j > 0);
return true;
}
private int E8(){
if (!E9(1)){
return -1;
}
int i = E1[0];
if (i != 0 && !E9(i)){
return -1;
}else{
return i;
}
}
private boolean E9(int i){
if (FA + i >= poolsize){
return false;
}
for (int j = 0; j < i; j++){
int k = C2[FA + j];
if (k < 0){
k += 256;
}
E1[j] = k;
}
FA += i;
return true;
}
private static final int EA(int i, int j){
return j << 8 | i;
}
private static final boolean EB(int i, int j){
return (i & j) == j;
}
}
public class GifFrame{
/* 保存gif中所有帧的向量 */
private Vector frames;
/* 当前播放的帧的索引 */
private int index;
public GifFrame(){
frames = new Vector(1);
index = 0;
}
/* 添加一帧 */
public void addImage(Bitmap image) {
frames.addElement(image);
}
/* 返回帧数 */
public int size() {
return frames.size();
}
/* 得到当前帧的图片 */
public Bitmap getImage() {
if (size() == 0) {
return null;
} else{
return (Bitmap) frames.elementAt(index);
}
}
/* 下一帧 */
public void nextFrame(){
if (index + 1 < size()){
index++;
}else{
index = 0;
}
}
/* 创建GifFrame */
public static GifFrame CreateGifImage(byte abyte0[]){
try{
GifFrame GF = new GifFrame();
Bitmap image = null;
GifDecoder gifdecoder = new GifDecoder(abyte0);
for (; gifdecoder.moreFrames(); gifdecoder.nextFrame()){
try{
image = gifdecoder.decodeImage();
if (GF != null && image != null){
GF.addImage(image);
}
continue;
}catch(Exception e){
e.printStackTrace();
}
break;
}
gifdecoder.clear();
gifdecoder = null;
return GF;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
public class GameView extends View implements Runnable{
Context mContext = null;
/* 声明GifFrame对象 */
GifFrame mGifFrame = null;
public GameView(Context context){
super(context);
mContext = context;
/* 解析GIF动画 */
mGifFrame=GifFrame.CreateGifImage(fileConnect(this.getResources().openRawResource(R.drawable.gif1)));
/* 开启线程 */
new Thread(this).start();
}
public void onDraw(Canvas canvas){
super.onDraw(canvas);
/* 下一帧 */
mGifFrame.nextFrame();
/* 得到当前帧的图片 */
Bitmap b=mGifFrame.getImage();
/* 绘制当前帧的图片 */
if(b!=null)
canvas.drawBitmap(b,10,10,null);
}
/**
* 线程处理
*/
public void run(){
while (!Thread.currentThread().isInterrupted()){
try{
Thread.sleep(100);
}catch (InterruptedException e){
Thread.currentThread().interrupt();
}
//使用postInvalidate可以直接在线程中更新界面
postInvalidate();
}
}
/* 读取文件 */
public byte[] fileConnect(InputStream is){
try{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch = 0;
while( (ch = is.read()) != -1) {
baos.write(ch);
}
byte[] datas = baos.toByteArray();
baos.close();
baos = null;
is.close();
is = null;
return datas;
}catch(Exception e){
return null;
}
}
}
public class Activity01 extends Activity{
private GameView mGameView = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mGameView = new GameView(this);
setContentView(mGameView);
}
}