4-Displaying Images with Android
在开始实际的游戏循环之前,让我们先显示一些图片,使得我们能够得到一些尺寸的概念(不太清楚这句什么意思)。如果还没有看到线程更新屏幕的内容,强烈推荐先看一下(上篇博客)
在android上面显示图片非常的简单
为了让问题简单,我们在左上角显示图形,我们需要 一个图片,我更喜欢png,我创建了一个名为droid_1.png的文件,大小是20*20 像素,你可以选择自己喜欢的工具,我用gimp或者ps
为了让程序可以使用,把图片拷贝到/res/drawable-mdpi目录下,我选择mdpi,它的意思是平常屏幕 中等密度,关于屏幕类型,可以求助 android文档
修改MainGamePanel文件,修改onDraw函数
1 protected void onDraw(Canvas canvas) { 2 canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.droid_1), 10, 10, null); 3 }
drawbitmap把droid_1画到了坐标10,10位置上
我们传递图片id给程序的资源管理部分以获得bitmap,当我们拷贝png到资源目录时候,插件会自动在R.java里面生成标识
也影响了线程的一部分code,检查下面的run函数
01 public void run() { 02 Canvas canvas; 03 Log.d(TAG, "Starting game loop"); 04 while (running) { 05 canvas = null; 06 // try locking the canvas for exclusive pixel editing on the surface 07 try { 08 canvas = this.surfaceHolder.lockCanvas(); 09 synchronized (surfaceHolder) { 10 // update game state 11 // draws the canvas on the panel 12 this.gamePanel.onDraw(canvas); 13 } 14 } finally { 15 // in case of an exception the surface is not left in 16 // an inconsistent state 17 if (canvas != null) { 18 surfaceHolder.unlockCanvasAndPost(canvas); 19 } 20 } // end finally 21 } 22 }
第二行 我们声明了我们要画图形的画布,画布是surface的图形要画的地方,也是我们编辑像素的地方,8行我们得到了画布,12行我们触发了onDraw函数,并把画布传递过去,注意这是同步块,别人是无法使用的。
这个函数很简单和基础,每次执行的时候,游戏循环得到画布,传递给game panel去画东西,game panel把图片画到坐标10,10处。回到FPS,如果每秒图片显示的次数低于20,就会被人注意到,我们的挑战就是保持到一定的水平以上,很快我们就会看到
运行一下代码,我们看到droid在左上角显示了
01 package net.obviam.droidz.model; 02 03 import android.graphics.Bitmap; 04 05 public class Droid { 06 07 private Bitmap bitmap; // the actual bitmap 08 private int x; // the X coordinate 09 private int y; // the Y coordinate 10 11 public Droid(Bitmap bitmap, int x, int y) { 12 this.bitmap = bitmap; 13 this.x = x; 14 this.y = y; 15 } 16 17 public Bitmap getBitmap() { 18 return bitmap; 19 } 20 public void setBitmap(Bitmap bitmap) { 21 this.bitmap = bitmap; 22 } 23 public int getX() { 24 return x; 25 } 26 public void setX(int x) { 27 this.x = x; 28 } 29 public int getY() { 30 return y; 31 } 32 public void setY(int y) { 33 this.y = y; 34 } 35 }
这是个只有几个属性和一个构造函数的简单类
droid的x和y坐标,还有要显示的bitmap
还没有什么特殊的,但是为了要运行起来,我们需要增加一些状态,为了保持简单,droid就有俩状态,被触摸和么有被触摸,触摸 就是说手指在屏幕上按着droid,按到droid的时候我们就保持触摸状态为真,否则就是假了
看一下新的droid的类
1 package net.obviam.droidz.model; 02 03 import android.graphics.Bitmap; 04 import android.graphics.Canvas; 05 import android.view.MotionEvent; 06 07 public class Droid { 08 09 private Bitmap bitmap; // the actual bitmap 10 private int x; // the X coordinate 11 private int y; // the Y coordinate 12 private boolean touched; // if droid is touched/picked up 13 14 public Droid(Bitmap bitmap, int x, int y) { 15 this.bitmap = bitmap; 16 this.x = x; 17 this.y = y; 18 } 19 20 public Bitmap getBitmap() { 21 return bitmap; 22 } 23 public void setBitmap(Bitmap bitmap) { 24 this.bitmap = bitmap; 25 } 26 public int getX() { 27 return x; 28 } 29 public void setX(int x) { 30 this.x = x; 31 } 32 public int getY() { 33 return y; 34 } 35 public void setY(int y) { 36 this.y = y; 37 } 38 39 public boolean isTouched() { 40 return touched; 41 } 42 43 public void setTouched(boolean touched) { 44 this.touched = touched; 45 } 46 47 public void draw(Canvas canvas) { 48 canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y - (bitmap.getHeight() / 2), null); 49 } 50 51 public void handleActionDown(int eventX, int eventY) { 52 if (eventX >= (x - bitmap.getWidth() / 2) && (eventX <= (x + bitmap.getWidth()/2))) { 53 if (eventY >= (y - bitmap.getHeight() / 2) && (y <= (y + bitmap.getHeight() / 2))) { 54 // droid touched 55 setTouched(true); 56 } else { 57 setTouched(false); 58 } 59 } else { 60 setTouched(false); 61 } 62 63 } 64 }
我们加入了touched来记录droid的状态
看一下MainGamePanel,变了可不少呢。
001 package net.obviam.droidz; 002 003 import net.obviam.droidz.model.Droid; 004 import android.app.Activity; 005 import android.content.Context; 006 import android.graphics.BitmapFactory; 007 import android.graphics.Canvas; 008 import android.graphics.Color; 009 import android.util.Log; 010 import android.view.MotionEvent; 011 import android.view.SurfaceHolder; 012 import android.view.SurfaceView; 013 014 public class MainGamePanel extends SurfaceView implements 015 SurfaceHolder.Callback { 016 017 private static final String TAG = MainGamePanel.class.getSimpleName(); 018 019 private MainThread thread; 020 private Droid droid; 021 022 public MainGamePanel(Context context) { 023 super(context); 024 // adding the callback (this) to the surface holder to intercept events 025 getHolder().addCallback(this); 026 027 // create droid and load bitmap 028 droid = new Droid(BitmapFactory.decodeResource(getResources(), R.drawable.droid_1), 50, 50); 029 030 // create the game loop thread 031 thread = new MainThread(getHolder(), this); 032 033 // make the GamePanel focusable so it can handle events 034 setFocusable(true); 035 } 036 037 @Override 038 public void surfaceChanged(SurfaceHolder holder, int format, int width, 039 int height) { 040 } 041 042 @Override 043 public void surfaceCreated(SurfaceHolder holder) { 044 // at this point the surface is created and 045 // we can safely start the game loop 046 thread.setRunning(true); 047 thread.start(); 048 } 049 050 @Override 051 public void surfaceDestroyed(SurfaceHolder holder) { 052 Log.d(TAG, "Surface is being destroyed"); 053 // tell the thread to shut down and wait for it to finish 054 // this is a clean shutdown 055 boolean retry = true; 056 while (retry) { 057 try { 058 thread.join(); 059 retry = false; 060 } catch (InterruptedException e) { 061 // try again shutting down the thread 062 } 063 } 064 Log.d(TAG, "Thread was shut down cleanly"); 065 } 066 067 @Override 068 public boolean onTouchEvent(MotionEvent event) { 069 if (event.getAction() == MotionEvent.ACTION_DOWN) { 070 // delegating event handling to the droid 071 droid.handleActionDown((int)event.getX(), (int)event.getY()); 072 073 // check if in the lower part of the screen we exit 074 if (event.getY() > getHeight() - 50) { 075 thread.setRunning(false); 076 ((Activity)getContext()).finish(); 077 } else { 078 Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY()); 079 } 080 } if (event.getAction() == MotionEvent.ACTION_MOVE) { 081 // the gestures 082 if (droid.isTouched()) { 083 // the droid was picked up and is being dragged 084 droid.setX((int)event.getX()); 085 droid.setY((int)event.getY()); 086 } 087 } if (event.getAction() == MotionEvent.ACTION_UP) { 088 // touch was released 089 if (droid.isTouched()) { 090 droid.setTouched(false); 091 } 092 } 093 return true; 094 } 095 096 @Override 097 protected void onDraw(Canvas canvas) { 098 // fills the canvas with black 099 canvas.drawColor(Color.BLACK); 100 droid.draw(canvas); 101 } 102 }
Line 28 creates the
droid
object at the the coordinates50,50.
It is declared as an attribute in line 20.In the
onTouchEvent
(method line 71) if the action is the touch of the screen (MotionEvent.ACTION_DOWN
) we want to know if our finger landed on the droid. To do this is easy. We need to check if the event’s coordinates are inside the droid’s bitmap. In order not to clutter theonTouch
event we just delegate this to the droid object. Now you can go back to theDroid.java
class and check thehandleActionDown
method.28行 创建了droid对象,在坐标50,50
在onTouchEvent方法中,如果触摸屏幕的动作,我们确认是否在droid上面,就是看看触摸事件的坐标是否在图形的坐标内。为了不让onTouchEvent杂乱,我们把这个放到droid对象中(其实我觉得在droid内部也可以,要是droid类是怎么设计的),现在回到droid.java,看一下handleActionDown方法。
public void handleActionDown(int eventX, int eventY) {if (eventX >= (x - bitmap.getWidth() / 2) && (eventX <= (x + bitmap.getWidth()/2))) {if (eventY >= (y - bitmap.getHeight() / 2) && (y <= (y + bitmap.getHeight() / 2))) {// droid touchedsetTouched(true);} else {setTouched(false);}} else {setTouched(false);}}很简单,如果在droid内部,把touched状态设成true。
回到onTouched方法,看一下MotionEvent.ACTION_MOVE,如果droid是touched,我们更新一下它的坐标。
而ondraw函数 就是把droid画到surface上。
就是这样