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:
tutorial2d-muliple-images

Go to Playing with graphics in Android – Part V

Share