In the last Android tutorial, we covered using a thread to draw to a Canvas in Android. In this tutorial, we’ll build further on that code to handle touchscreen events, and specifically to move the drifting bubble around the screen in response to the user touching the screen.
When doing anything coordinate-based on Android, it’s wise to check the origin and direction of the coordinate system before you start. Not all of them use the same setup! However, in this case, the touchscreen coordinates are the same as the standard 2D coordinates: The origin is in the top left corner, with the x-axis running rightwards, and the y-axis running downwards. This means that we can use the touch coordinates directly as screen coordinates, without any transformation. (This is not the case if you’re using OpenGL.)
Adding TouchEvent Handling to the View
We’ll use the same View as last time, BubbleSurfaceView. All we need to do is to add an onTouchEvent()
method:
public class BubbleSurfaceView extends SurfaceView implements SurfaceHolder.Callback { public boolean onTouchEvent(MotionEvent e) { float touchX = e.getX(); float touchY = e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: thread.setBubble(touchX, touchY); break; } return true; }
onTouchEvent()
is part of the SurfaceView spec, so the MotionEvent information will automatically be passed into your SurfaceView. All you need to do is to write a method to handle it. With a single case, as here, there’s no real need to create the touchX
and touchY
values (you could use e.getX()
and e.getY()
directly); but it’s good practice to use these values for ease of maintenance as your code gets more complicated.
This code reacts to an ACTION_DOWN
event (and only to that event); that is, when the user puts their finger down on the screen. Since (as per last tutorial) the actual drawing on our Canvas is being handled by an independent thread, we need to pass the touch information into the BubbleThread. BubbleThread.setBubble()
is straightforward:
class BubbleThread extends Thread { protected void setBubble(float x, float y) { bubbleX = x; bubbleY = y; }
bubbleX
and bubbleY
hold the central co-ordinates of our bubble. As the run()
method in this Thread is constantly redrawing the screen, once the bubble’s co-ordinates are reset, it will be redrawn at the new location.
Compile the code and run it on your phone to see the bubble being redrawn wherever you touch the screen (and then drifting from that position, as in the code from the last tutorial).
Handling Other Motion Events
We can easily change this to handle ACTION_UP
instead; just substitute ACTION_UP
for ACTION_DOWN
in the above code, compile and run, and you’ll see that now the bubble only moves position when you lift your finger.
What about ACTION_MOVE
? Can we change this method to make the bubble follow your finger as you pull it around the screen? The API makes this too very straighforward. EditonTouchEvent()
to look like this:
public boolean onTouchEvent(MotionEvent e) { float touchX = e.getX(); float touchY = e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_DOWN: thread.setBubble(touchX, touchY); break; case MotionEvent.ACTION_MOVE: thread.setBubble(touchX, touchY); break; case MotionEvent.ACTION_UP: thread.setBubble(touchX, touchY); break; } return true; }
ACTION_MOVE
is a constant returned every time there is a change during a gesture; ie between an ACTION_DOWN
and ACTION_UP
event. So every time you move your finger a bit on the screen, a MotionEvent is returned, and we can get the coordinates of your finger’s new position and change the bubble location in the drawing thread. Once the event is over (anACTION_UP
event happens), we stop sending new bubble locations, and the bubble goes back to drifting.
There are a huge number of other MotionEvents, too, all of which are described in the MotionEvent API. As you’ll see there, you can also get other information about touch events. Here’s a quick example of how to get the length of time that a series of touch events has taken, again by editing onTouchEvent()
:
public boolean onTouchEvent(MotionEvent e) { // get touchX and touchY switch (e.getAction()) { // .... other cases ... case MotionEvent.ACTION_UP: thread.setBubble(touchX, touchY); float totalTime = e.getEventTime() - e.getDownTime(); Toast.makeText(ctx, "Touch time in ms was " + totalTime, Toast.LENGTH_SHORT).show(); break; } return true; }
getDownTime()
returns the time (in milliseconds) of the down touch that started the current chain of motion events. getEventTime()
returns the time (in milliseconds) of the current motion event. Here, then, we can get the duration for which the user’s finger was dragging the bubble around the screen (the time between that first down event, and the current up event), and pop up a Toast message to tell them.
Finally, a quick practical note about threading. It is possible, if drawing to the canvas takes any significant time, to start the run()
method while the thread is still running, and try to draw the canvas after it has been stopped, thus throwing an error. The more complicated your code gets, the more likely it is that this will happen. To avoid this, add a check todoDraw()
:
private void doDraw(Canvas canvas) { if (run) { canvas.restore(); canvas.drawColor(Color.BLACK); canvas.drawCircle(bubbleX, bubbleY, 50, paint); } }
Being able to react to motion and touch events is an essential part of making the most of the Android API, and there is a range of complex information available about touch events to allow you to make the most of them. Play around with the information available from the API to see what else you can make the bubble do, or what else you can do with it. In the next tutorial, we’ll take a look at multi-touch events: having more than one finger on the screen.