You are new to this series? Please start with the first part.
The fourth part of the series will show you how to add more bitmaps on your screen.
First we have to add is graphic class containing a bitmap and the coordinates where it is located.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class GraphicObject { private Bitmap _bitmap; private Coordinates _coordinates; public GraphicObject(Bitmap bitmap) { _bitmap = bitmap; _coordinates = new Coordinates(); } public Bitmap getGraphic() { return _bitmap; } public Coordinates getCoordinates() { return _coordinates; } /** * Contains the coordinates of the graphic. */ public class Coordinates { private int _x = 100; private int _y = 0; public int getX() { return _x + _bitmap.getWidth() / 2; } public void setX(int value) { _x = value - _bitmap.getWidth() / 2; } public int getY() { return _y + _bitmap.getHeight() / 2; } public void setY(int value) { _y = value - _bitmap.getHeight() / 2; } public String toString() { return "Coordinates: (" + _x + "/" + _y + ")"; } } } |
Next is a new variable which will contain all of our added GraphicObjects. We will use an ArrayList because we don’t know how many bitmaps we will add during our run. Every GraphicObject instance will store their own coordinates, so we can remove the _x and _y variable in our Panel class.
1 2 3 4 5 | class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; private ArrayList<GraphicObject> _graphics = new ArrayList<GraphicObject>(); // code snipped } |
Now we will modify the onTouchEvent() method to add a new element at the location we touched. The onDraw() method will iterate over every GraphicObject in our ArrayList and will draw it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Override public boolean onTouchEvent(MotionEvent event) { GraphicObject graphic = new GraphicObject(BitmapFactory.decodeResource(getResources(), R.drawable.icon)); graphic.getCoordinates().setX((int) event.getX() - graphic.getGraphic().getWidth() / 2); graphic.getCoordinates().setY((int) event.getY() - graphic.getGraphic().getHeight() / 2); return _graphics.add(graphic); } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); Bitmap bitmap; Coordinates coords; for (GraphicObject graphic : _graphics) { bitmap = graphic.getGraphic(); coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } } |
Our panel now looks like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; private ArrayList<GraphicObject> _graphics = new ArrayList<GraphicObject>(); public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); setFocusable(true); } @Override public boolean onTouchEvent(MotionEvent event) { GraphicObject graphic = new GraphicObject(BitmapFactory.decodeResource(getResources(), R.drawable.icon)); graphic.getCoordinates().setX((int) event.getX() - graphic.getGraphic().getWidth() / 2); graphic.getCoordinates().setY((int) event.getY() - graphic.getGraphic().getHeight() / 2); return _graphics.add(graphic); } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); Bitmap bitmap; GraphicObject.Coordinates coords; for (GraphicObject graphic : _graphics) { bitmap = graphic.getGraphic(); coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } } |
Please compile and run the application. Try to add 40 bitmaps on different locations…
As you will recognize it is not possible. The application will crash. The chance the application crashes increase with the number of bitmaps on the screen.
The reason therefor is simple: if the application runs in the for loop of the onDraw() method and you touch in this very moment the screen, you will generate a ConcurrentModificationException which means, you change the number of elements in the list while iterating over the list.
To prevent that from happening, we should use the synchronization with the _surfaceHolder.
Add a getter for the _surfaceHolder to the TutorialThread class.
1 2 3 | public SurfaceHolder getSurfaceHolder() { return _surfaceHolder; } |
And modify the onTouchEvent() method until it look like this:
1 2 3 4 5 6 7 8 9 10 | @Override public boolean onTouchEvent(MotionEvent event) { synchronized (_thread.getSurfaceHolder()) { GraphicObject graphic = new GraphicObject(BitmapFactory.decodeResource(getResources(), R.drawable.icon)); graphic.getCoordinates().setX((int) event.getX() - graphic.getGraphic().getWidth() / 2); graphic.getCoordinates().setY((int) event.getY() - graphic.getGraphic().getHeight() / 2); _graphics.add(graphic); return true; } } |
Try it now and you will see, the application runs stable.
Note:
Maybe you already recognized, that that you can add bitmaps by simply moving the finger over the touch screen. Thats because we add a bitmap on every event not only if it was a click.
To change that behavior simply add an if statement to the onTouchEvent() method.
1 2 3 4 5 6 7 8 9 10 11 12 | @Override public boolean onTouchEvent(MotionEvent event) { synchronized (_thread.getSurfaceHolder()) { if (event.getAction() == MotionEvent.ACTION_DOWN) { GraphicObject graphic = new GraphicObject(BitmapFactory.decodeResource(getResources(), R.drawable.icon)); graphic.getCoordinates().setX((int) event.getX() - graphic.getGraphic().getWidth() / 2); graphic.getCoordinates().setY((int) event.getY() - graphic.getGraphic().getHeight() / 2); _graphics.add(graphic); } return true; } } |
Full source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | package com.droidnova.android.tutorial2d; import java.util.ArrayList; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; public class Tutorial2D extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new Panel(this)); } class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; private ArrayList<GraphicObject> _graphics = new ArrayList<GraphicObject>(); public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); setFocusable(true); } @Override public boolean onTouchEvent(MotionEvent event) { synchronized (_thread.getSurfaceHolder()) { if (event.getAction() == MotionEvent.ACTION_DOWN) { GraphicObject graphic = new GraphicObject(BitmapFactory.decodeResource(getResources(), R.drawable.icon)); graphic.getCoordinates().setX((int) event.getX() - graphic.getGraphic().getWidth() / 2); graphic.getCoordinates().setY((int) event.getY() - graphic.getGraphic().getHeight() / 2); _graphics.add(graphic); } return true; } } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); Bitmap bitmap; GraphicObject.Coordinates coords; for (GraphicObject graphic : _graphics) { bitmap = graphic.getGraphic(); coords = graphic.getCoordinates(); canvas.drawBitmap(bitmap, coords.getX(), coords.getY(), null); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } } class TutorialThread extends Thread { private SurfaceHolder _surfaceHolder; private Panel _panel; private boolean _run = false; public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { _run = run; } public SurfaceHolder getSurfaceHolder() { return _surfaceHolder; } @Override public void run() { Canvas c; while (_run) { c = null; try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } } } class GraphicObject { /** * Contains the coordinates of the graphic. */ public class Coordinates { private int _x = 100; private int _y = 0; public int getX() { return _x + _bitmap.getWidth() / 2; } public void setX(int value) { _x = value - _bitmap.getWidth() / 2; } public int getY() { return _y + _bitmap.getHeight() / 2; } public void setY(int value) { _y = value - _bitmap.getHeight() / 2; } public String toString() { return "Coordinates: (" + _x + "/" + _y + ")"; } } private Bitmap _bitmap; private Coordinates _coordinates; public GraphicObject(Bitmap bitmap) { _bitmap = bitmap; _coordinates = new Coordinates(); } public Bitmap getGraphic() { return _bitmap; } public Coordinates getCoordinates() { return _coordinates; } } } |
Screenshot:

Comments
Leave a comment Trackback