What You’ll Learn in This Hour
• Using single-touch input
• Listening for touch inputs
• Using multi-touch input
This hour covers the touchscreen: getting both single and multiple touch events from an Android device. Although Android supports a soft keyboard and a hardware keyboard (via Bluetooth or USB), we will concern ourselves only with touch-screen input because that is the most prevalent form of input on Android 4.0 devices.
There are several ways to get user input on an Android device. The most common form of input on the newest devices is with a multi-touch-capable screen. Additionally, Android supports input via keyboard. The accelerometer and compass might be considered inputs as well. All devices that support Android 4.0 will have a touch screen and soft keyboard, but a Bluetooth keyboard may also be used for input. Most new games will feature touch input, although it would make sense for less interactive apps to make use of a keyboard. Because keyboard input is not going to be common in touch-enabled video games, we won’t add keyboard input to our “toolbox.”
To support touch input, you need to import OnTouchListener
, a subclass below View
, like so:
import android.view.View.OnTouchListener;
Next, a class must implement OnTouchListener
to receive input events. The most common class to implement it will be your main Activity
class (which we have been calling Main
or Game
). You could also create a custom new class designed solely to handle input, but I have found that to require some extra steps with little benefit. What we’re really going for is a Game
class that already has a lot of reusable code in it that we can use to make new games fairly easily.
In other words, the long-term perspective is that we’ll eventually inherit from our own Game
class instead of from Activity
, and it will contain quite a bit of useful game code.
In the class definition, you will need to add implements OnTouchListener
to the class definition. Here is how we will add touch input support to an Activity
-based program:
public class Game extends Activity implements OnTouchListener
When adding implements OnTouchListener
to a class, that class must implement a method called onTouch()
. Here it is:
@Override public boolean onTouch(View v, MotionEvent event) {
return false;
}
This is the default form of onTouch()
, which returns false. This tells the calling class (which generates onTouch()
events for us) that it should handle the input event because we did not do anything with it ourselves (speaking as the implemented method here). When you do handle the input event and want to conclude it, return false to tell the caller that no more input processing is needed.
You will be able to test your game projects with the Android emulator using the mouse to simulate single-touch input, but it will be impossible to test multi-touch with the emulator alone. Given the emulator’s performance challenges, I strongly recommend using a physical Android device for serious development—games or apps. It comes up instantly on a physical device, whereas the emulator takes quite a few seconds to respond. Of course, if your game designs call only for mouselike input, it’s not a problem.
The MotionEvent
parameter contains all the info we need to discern where a touch event occurred. Most touch events will be treated as a MOVE event, with an additional event when you lift your finger, generating an UP event.
The complete source code for a program that demonstrates single-touch input is listed next. Figure 9.1 shows the program with a MOVE event registered (while touching or dragging a finger across the display), and Figure 9.2 shows the program after releasing the touch.
package android.program;
import java.io.*;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.*;
import android.os.Bundle;
import android.view.*;
import android.view.View.OnTouchListener;
public class Game extends Activity implements OnTouchListener {
DrawView drawView;
//input variables
Point touch = new Point(0,0);
String inputAction = "";
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
drawView = new DrawView(this);
setContentView(drawView);
//turn on touch listening
drawView.setOnTouchListener(this);
}
@Override public void onResume() {
super.onResume();
drawView.resume();
}
@Override public void onPause() {
super.onPause();
drawView.pause();
}
//touch listener event
@Override public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
inputAction = "DOWN";
break;
case MotionEvent.ACTION_MOVE:
inputAction = "MOVE";
break;
case MotionEvent.ACTION_UP:
inputAction = "UP";
break;
}
//remember this touch position
touch.x = (int)event.getX();
touch.y = (int)event.getY();
//notify touch handler that we used it
return true;
}
public class DrawView extends SurfaceView implements Runnable
{
//game loop thread
Thread gameloop = null;
//surface holder
SurfaceHolder surface = null;
//thread-safe running var
volatile boolean running = false;
//asset manager
AssetManager assets = null;
//constructor method
public DrawView(Context context) {
super(context);
//get the SurfaceHolder object
surface = getHolder();
//create the asset manager
assets = context.getAssets();
}
public void resume() {
running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
running = false;
while (true) {
try {
gameloop.join();
}
catch (InterruptedException e) { }
}
}
//thread run method
@Override public void run() {
while (running) {
if (!surface.getSurface().isValid()) continue;
//open the canvas for drawing
Canvas canvas = surface.lockCanvas();
canvas.drawColor(Color.BLACK);
//draw circle at touch position
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(24);
canvas.drawText("Touch screen to test single touch input",
10, 20, paint);
canvas.drawText("Action: " + inputAction, 10, 50, paint);
canvas.drawText("Position: " + touch.x + "," + touch.y,
10, 80, paint);
if (touch.x != 0 && touch.y != 0)
canvas.drawCircle(touch.x, touch.y, 50, paint);
//close the canvas
surface.unlockCanvasAndPost(canvas);
try {
Thread.sleep(20);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
The latest version of the Android SDK (at the time of this writing) allows you to tether the emulator to a physical Android device to test multi-touch functionality. This sounds like an odd suggestion, but the real benefit that the emulator affords a developer is the capability to test code on a variety of devices with different screen resolutions. Having any Android device for development, even an older one (not running the target OS version) can be a benefit. You can, for instance, run your code on the emulator running an Android 4.0 “tablet” device, while using your tethered Android phone for multi-touch. The screen resolutions do not have to match, nor does the OS version have to be the same.
The multi-touch input system is a bit convoluted, but nothing we can’t handle. The way it used to work was by using a pointer identifier to represent each touch input source. Rather than return an array of input points, Android gave an identifier that was then used as an index. Fortunately, later versions of the SDK eliminated this strange behavior and switched to a simple indexed system.
Here is a very quick example that reports the position of multiple touch pointers using the log, just to give you the basic method calls at a glance. For multi-touch input, you need to iterate through each touch input, where the number of inputs is returned by MotionEvent.getPointerCount()
. After that, you can use overloads of getX()
and getY()
with the index of the input to get its position.
@Override public boolean onTouch(View v, MotionEvent event) {
int numPoints = event.getPointerCount();
for (int n=0; n<numPoints; n++) {
int x = (int)event.getX(n);
int y = (int)event.getY(n);
Log.d("Pointer", "Touch " + n + ": x=" + event.getX(n) +", " +
y="+event.getY(n));
}
return true;
}
To see the output of the Log
, you need to switch to the DDMS perspective. The normal editing environment is called the Java perspective. You should see the perspectives at the upper-right corner of the Eclipse window, as shown in Figure 9.3. This figure also shows the log output window at the bottom. You can use Log
anytime you want to quickly display information without going through the trouble of outputting text to the screen using Context.drawText()
. Most coders don’t normally work with Eclipse in this small of a window—it was resized for the figure.
To test this code properly, you must use a real Android device with a multi-touch screen, even if just to tether it to the emulator.
The simple example was pretty straightforward, but we can still improve on this code by molding it into a shape that is easier to reuse, requiring less Android work, so to speak, to get input in a game project. The quick example showed the basic data we would expect from multi-touch input, where several points of input are reported. What we want to do is get the values from multiple touch inputs and store those values so they can be used in any way we want during that game loop cycle (that is, during that “frame”).
First, we’ll create an array to keep track of touch points:
Point[] points;
We’ll assume that 5
is the maximum number of inputs. Realistically, you won’t need more than this for most games. The exception might be a larger tablet with a game that supports multiple players using the same tablet. Yes, you can do that—support up to five local players—but each player would get only one input, and they could interfere with each other by using two fingers.
Next, initialize the array:
points = new Point[5];
for (int n=0; n<5; n++) {
points[n] = new Point(0,0);
}
Now, inside the onTouch()
event, we can fill the points array with touch input point values that can be used at any time. It is more efficient this way, rather than going back to the MotionEvent
parameter each time. You could probably create a global copy of MotionEvent
, but all you need are the points!
To ensure that we don’t go out of the array’s bounds, we’ll get the number of touch points and adjust the total if needed. That is being handled by a global int
called numPoints
:
numPoints = event.getPointerCount();
if (numPoints > 5) numPoints = 5;
If an Android device supports more than five touch inputs at a time, you may want to support that device by modifying the code. This is just a quick example to show how it works!
Next, we grab the touch points out of the MotionEvent
parameter:
for (int n=0; n<numPoints; n++) {
points[n].x = (int)event.getX(n);
points[n].y = (int)event.getY(n);
}
The Multi-touch Demo is shown in Figure 9.4. This example shows how to read the touchscreen for up to five touch input points. The index number and position of each point is displayed, and a circle is drawn at each point, using a different color for each point.
package android.program;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.*;
import android.os.Bundle;
import android.view.*;
import android.view.View.OnTouchListener;
public class Game extends Activity implements OnTouchListener {
DrawView drawView = null;
Paint paint = null;
Point[] points;
int numPoints=0;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//create the view object
drawView = new DrawView(this);
setContentView(drawView);
//create a paint object
paint = new Paint();
paint.setTextSize(24);
//turn on touch listening
drawView.setOnTouchListener(this);
//create the points array
points = new Point[5];
for (int n=0; n<5; n++) {
points[n] = new Point(0,0);
}
}
@Override public void onResume() {
super.onResume();
drawView.resume();
}
@Override public void onPause() {
super.onPause();
drawView.pause();
}
//touch listener event
@Override public boolean onTouch(View v, MotionEvent event) {
//count the touch inputs
numPoints = event.getPointerCount();
if (numPoints > 5) numPoints = 5;
//store the input values
for (int n=0; n<numPoints; n++) {
points[n].x = (int)event.getX(n);
points[n].y = (int)event.getY(n);
}
return true;
}
public class DrawView extends SurfaceView implements Runnable {
Thread gameloop = null;
SurfaceHolder surface = null;
volatile boolean running = false;
AssetManager assets = null;
int[] colors;
public DrawView(Context context) {
super(context);
surface = getHolder();
assets = context.getAssets();
//create an array of colors
colors = new int[5];
colors[0] = Color.GREEN;
colors[1] = Color.MAGENTA;
colors[2] = Color.YELLOW;
colors[3] = Color.RED;
colors[4] = Color.CYAN;
}
public void resume() {
running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
running = false;
while (true) {
try {
gameloop.join();
}
catch (InterruptedException e) { }
}
}
@Override public void run() {
while (running) {
if (!surface.getSurface().isValid()) continue;
Canvas canvas = surface.lockCanvas();
canvas.drawColor(Color.BLACK);
paint.setColor(Color.WHITE);
canvas.drawText("Multi touch input demo", 10, 20,
paint);
canvas.drawText("Number of inputs: " + numPoints,
10, 50, paint);
int y=80;
for (int n=0; n<numPoints; n++) {
paint.setColor(colors[n]);
//display point values
String s = " " + n + ": " + points[n].toString();
canvas.drawText(s, 10, y, paint);
y += 30;
//draw a circle at each point
if (points[n].x != 0 && points[n].y != 0)
canvas.drawCircle(points[n].x, points[n].y,
50, paint);
}
surface.unlockCanvasAndPost(canvas);
try {
Thread.sleep(20);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
This hour taught you how to use the touch screen. You learned how to get single-touch input (which is similar to mouse input). You also learned how to get multiple touch input by detecting the number of touch inputs and reading the touch positions. The code to get touch input was fairly easy to write, but we could still improve on it to make it easier to use. We will keep this code handy for the game framework project coming up in a future hour.
Q. User input is a more complex subject than it first appears to a programmer. From a design perspective, user input is crucial and not just a matter of getting touch coordinates. The user interface must be designed with fun in mind. The term for the study of fun in gameplay is called Funativity. Discuss this topic. You may want to search online references for more information.
A. Answers will vary.
Q. What types of games most benefit from single-touch input, and what types benefit most from multi-touch input? Discuss the differences among games that use the two methods. What are perhaps the most significant examples in the game market of each?
A. Answers will vary.
1. What is the primary interface class that gives access to touch input?
2. What method (usually called from Activity.onCreate()
) initializes the touch input system?
3. Which event method reports touch input events?
1. OnTouchListener
2. SurfaceView.setOnTouchListener()
3. OnTouchListener.onTouch()
Write a pair of helper methods that encapsulate touch input for future use. Give them any name you want, but make them return the results of single touch and multiple touch inputs. You should be able to call these functions and receive back (as a return value) the coordinates, or null if no input was detected.
Hint: To make this work, some globals will need to be used.
3.129.217.5