Playing with graphics in Android – Part III
by Martin on May.18, 2009, under tutorial
You are new to this series? Please start with the first part.
The third part of this series will add some interactivity to our little application. We will add the possibility the icon appears at the point you touch the screen.
Just to remember, we have 3 classes: Tutorial2D (our Activity class), Panel (our SurfaceView class) and TutorialThread (our Thread class).
Our interaction will be handled in our Panel class, so we must be sure, that the touch events will processed by the Panel class. To ensure that, we simply have to set the focus to our panel (line 5).
1 2 3 4 5 6 | public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); setFocusable(true); } |
To show the bitmap on different locations, we have to add 2 variables for the x and y coordinates and we have to modify the onDraw() method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; private int _x = 20; private int _y = 20; // code snipped @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, _x, _y, null); } // code snipped } |
Finally we need to override the onTouchEvent() where we get the coordinates of the touch and set our variables.
1 2 3 4 5 6 | @Override public boolean onTouchEvent(MotionEvent event) { _x = (int) event.getX(); _y = (int) event.getY(); return true; } |
Now we can compile and start our application.
You will see, that the application will draw the bitmap not exactly where you touch the screen. The reason is that the coordinates are for the upper left corner of the bitmap. To change that, we have to use a little bit math.
1 2 3 4 5 6 | @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, _x - (_scratch.getWidth() / 2), _y - (_scratch.getHeight() / 2), null); } |
As you see, we have to subtract half of the bitmap width from x and half of bitmap height from y to get the middle of the bitmap on the point we touched the screen.
Finally our Panel class looks like this:
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 | class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; private int _x = 20; private int _y = 20; public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); setFocusable(true); } @Override public boolean onTouchEvent(MotionEvent event) { _x = (int) event.getX(); _y = (int) event.getY(); return true; } @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, _x - (_scratch.getWidth() / 2), _y - (_scratch.getHeight() / 2), 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... } } } } |
June 23rd, 2009 on 3:55 pm
Thanks for the good post.
What are your thoughts on placing the onTouchListener in the thread instead of the panel?
June 23rd, 2009 on 7:15 pm
I always think about the fact, who is responsible for what. In this case I ask you: What does the game loop have to do with touch events?
I don’t touch the game loop, but I touch the view which means the panel… also the focus is on the view.
To handle touch events in the thread is possible, but I wouldn’t recommend that.
July 7th, 2009 on 6:00 am
Hey Martin,
Hello, i am a beginner in Android field. I just saw your post and i just liked it. I wanted to know that who calls he onDraw() method.
July 8th, 2009 on 8:20 am
Hey Udai,
as I mentioned in the second part, we have a thread class called TutorialThread. In this class, we call the onDraw() method.
July 14th, 2009 on 10:10 am
Thanks Martin,
But i meant that how onDraw() method is called internally…and if possible can we call it by our own as per our requirement..
July 14th, 2009 on 12:48 pm
Of course we can call it by ourself.
How Android use the View is explained on How Android Draws Views.
July 26th, 2009 on 6:56 pm
Fantastic post and I agree with you on the on touch event being associated with the view. Extremely well done article and I am happy to see that you follow good design principles.
July 29th, 2009 on 7:12 pm
Great series Martin,
keep up the tutorials!
September 22nd, 2009 on 11:58 pm
hi Martin,
The series have been very good. They are really helpful. however, isn’t there a need to synchronize the integer variable when you modify them in the “OnTouchEvent” ? The thread continuously accesses the value of _x and _y, and all of a sudden it is changed by some its parent thread. Is there a need for mutual exclusion ?? or is it an overkill ???
December 24th, 2009 on 3:05 am
Hey I had great luck with parts 1 and 2 but somehow it is telling me “the method onTouchEvent must override a supertype method” regarding the code
”
@Override
public boolean onTouchEvent(MotionEvent event) ”
I’d think “onTouchEvent” would be super enough.
what am I doing wrong?
December 24th, 2009 on 3:30 pm
Your Java is not uptodate.
There are two possible solutions:
1. Simply remove the @Override annotations
2. (the better solution) update your java sdk to version 1.6
January 12th, 2010 on 2:09 pm
Thank you for this and other fantastic tutorials.
Keep up the good work!
January 29th, 2010 on 6:29 am
Hey, thanks for the great tutorials. I too have the “the method onTouchEvent must override a supertype method” error, and my java is up to date. I have the latest version of the JDK, the latest version of Eclipse, and the latest version of the Android SDK. Any suggestions?
Thanks,
Rob
January 29th, 2010 on 12:44 pm
Hi Rob.
Maybe you have an old Java installation somewhere around and your Eclipse is using this one?
Check in you cmd/console the output of
Another possibility is, you have the wrong compiler compliance level. Check this in Eclipse -> Preferences -> Java -> Compiler. The compliance level should be 1.6.
Hope it solves the problem
January 31st, 2010 on 6:10 pm
I am a little confused on what tells the app to redraw the canvas when the screen is touched.
January 31st, 2010 on 6:30 pm
The screen will be automatically redrawn on every run of the thread loop. Check the class TutorialThread.
January 31st, 2010 on 6:34 pm
Oh, so even when we don’t click the screen the canvas is redrawn with the image in the same place?
January 31st, 2010 on 6:42 pm
Your right! Its not necessary at the moment, but at the end of the series, you will see the reason (also mentioned in some comments there).
January 31st, 2010 on 6:48 pm
Ok, I understand it now. Tanks Martin for your help and for the great tutorials!
February 18th, 2010 on 2:48 pm
mind blowing… really very nice article..
February 23rd, 2010 on 9:31 pm
Mate this is very very good! Please keep up the posts. Thanks
February 23rd, 2010 on 9:32 pm
I ran the onTouch event code and it compiles. This might be a silly question but I guess since its running in the emulator, there shouldn’t be a change is the position of the image.. should it?
March 1st, 2010 on 3:35 pm
Sorry, but I really don’t understand what you are trying to say.
Can you be more specific?