CompassHandler

Just for fun, we're going to provide an example that is similar to the AccelerometerHandler, but this time we'll give you the compass values along with the pitch and roll of the phone. We'll call the compass value “yaw,” since that's a standard orientation term that nicely defines the value we're seeing.

The only difference between the following code snippet and the previous accelerometer example is the change of the sensor type to TYPE_ORIENTATION and the renaming of the fields from “accel” to “yaw, pitch, and roll.” Otherwise, it works in the same way, and you can easily swap this code into the game as the control handler! Listing 5–6 shows you its code.

Listing 5–6. AccelerometerHandler.java; Performing All the Accelerometer Handling

package com.badlogic.androidgames.framework.impl;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class CompassHandler implements SensorEventListener {
    float yaw;
    float pitch;
    float roll;

    public CompassHandler(Context context) {
        SensorManager manager = (SensorManager) context
                .getSystemService(Context.SENSOR_SERVICE);
        if (manager.getSensorList(Sensor.TYPE_ORIENTATION).size() != 0) {
            Sensor compass = manager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
            manager.registerListener(this, compass,
                    SensorManager.SENSOR_DELAY_GAME);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // nothing to do here
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        yaw = event.values[0];
        pitch = event.values[1];
        roll = event.values[2];
    }

    public float getYaw() {
        return yaw;
    }

    public float getPitch() {
        return pitch;
    }

    public float getRoll() {
        return roll;
    }
}

The Pool Class: Because Reuse Is Good for You!

What's the worst thing that can happen to us as Android developers? World-stopping garbage collection! If you look at the Input interface definition in Chapter 3, you'll find the getTouchEvents() and getKeyEvents() methods. These methods return TouchEvents and KeyEvents lists. In our keyboard and touch event handlers, we constantly create instances of these two classes and store them in lists that are internal to the handlers. The Android input system fires many of these events when a key is pressed or a finger touches the screen, so we constantly create new instances that are collected by the garbage collector in short intervals. In order to avoid this, we implement a concept known as instance pooling. Instead of repeatedly creating new instances of a class, we simply reuse previously-created instances. The Pool class is a convenient way to implement that behavior. Let's have a look at its code in Listing 5–7.

Listing 5–7. Pool.java; Playing Well with the Garbage Collector

package com.badlogic.androidgames.framework;

import java.util.ArrayList;
import java.util.List;

public class Pool<T> {

Here are the generics: the first thing to recognize is that this is a generically typed class, much like collection classes such as ArrayList or HashMap. Generics allow us to store any type of object in our Pool without having to cast continuously. So what does the Pool class do?

    public interface PoolObjectFactory<T> {
        public T createObject();
    }

An interface called PoolObjectFactory is the first thing defined and is, once again, generic. It has a single method, createObject() that will return a new object with the generic type of the Pool/PoolObjectFactory instance.

    private final List<T> freeObjects;
    private final PoolObjectFactory<T> factory;
    private final int maxSize;

The Pool class has three members. These include an ArrayList to store pooled objects, a PoolObjectFactory that is used to generate new instances of the type held by the class, and a member that stores the maximum number of objects the Pool can hold. The last bit is needed so our Pool does not grow indefinitely; otherwise, we might run into an out-of-memory exception.

    public Pool(PoolObjectFactory<T> factory, int maxSize) {
        this.factory = factory;
        this.maxSize = maxSize;
        this.freeObjects = new ArrayList<T>(maxSize);
    }

The constructor of the Pool class takes a PoolObjectFactory and the maximum number of objects it should store. We store both parameters in the respective members and instantiate a new ArrayList with the capacity set to the maximum number of objects.

public T newObject() {
        T object = null;

if (freeObjects.size() == 0)
            object = factory.createObject();
else
            object = freeObjects.remove(freeObjects.size() - 1);

return object;
    }

The newObject() method is responsible for either handing us a brand-new instance of the type held by the Pool, via the PoolObjectFactory.newObject() method, or it returns a pooled instance in case there's one in the freeObjectsArrayList. If we use this method, we get recycled objects as long as the Pool has some stored in the freeObjects list. Otherwise, the method creates a new one via the factory.

    public void free(T object) {
        if (freeObjects.size() < maxSize)
            freeObjects.add(object);
    }
}

The free() method lets us reinsert objects that we no longer use. It simply inserts the object into the freeObjects list if it is not yet filled to capacity. If the list is full, the object is not added, and it is likely to be consumed by the garbage collector the next time it executes.

So, how can we use that class? We'll look at some pseudocode usage of the Pool class in conjunction with touch events.

PoolObjectFactory<TouchEvent> factory = new PoolObjectFactory<TouchEvent>() {
    @Override
    public TouchEvent createObject() {
        return new TouchEvent();
    }
};
Pool<TouchEvent> touchEventPool = new Pool<TouchEvent>(factory, 50);
TouchEvent touchEvent = touchEventPool.newObject();
– do something here –
touchEventPool.free(touchEvent);

First, we define a PoolObjectFactory that creates TouchEvent instances. Next we instantiate the Pool by telling it to use our factory and that it should maximally store 50 TouchEvents. When we want a new TouchEvent from the Pool, we call the Pool's newObject() method. Initially, the Pool is empty, so it will ask the factory to create a brand new TouchEvent instance. When we no longer need the TouchEvent, we reinsert it into the Pool by calling the Pool's free() method. The next time we call the newObject() method, we get the same TouchEvent instance and recycle it to avoid problems with the garbage collector. This class is useful in a couple places. Please note that you must be careful to fully reinitialize reused when they're fetched from the Pool.

KeyboardHandler: Up, Up, Down, Down, Left, Right …

The KeyboardHandler must fulfill a couple tasks. First, it must connect with the View from which keyboard events are to be received. Next, it must store the current state of each key for polling. It must also keep a list of KeyEvent instances that we designed in Chapter 3 for event-based input handling. Finally, it must properly synchronize everything since it will receive events on the UI thread while being polled from our main game loop, which is executed on a different thread. This is a lot of work! As a refresher, we'll show you the KeyEvent class that we defined in Chapter 3 as part of the Input interface.

public static class KeyEvent {
    public static final int KEY_DOWN = 0;
    public static final int KEY_UP = 1;

    public int type;
    public int keyCode;
    public char keyChar;
}

This class simply defines two constants that encode the key event type along with three members while holding the type, key code, and Unicode character of the event. With this, we can implement our handler.

Listing 5–8 shows the implementation of the handler with the Android APIs discussed earlier and our new Pool class.

Listing 5–8. KeyboardHandler.java: Handling Keys Since 2010

package com.badlogic.androidgames.framework.impl;

import java.util.ArrayList;
import java.util.List;

import android.view.View;
import android.view.View.OnKeyListener;

import com.badlogic.androidgames.framework.Input.KeyEvent;
import com.badlogic.androidgames.framework.Pool;
import com.badlogic.androidgames.framework.Pool.PoolObjectFactory;

public class KeyboardHandler implements OnKeyListener {
    boolean[] pressedKeys = new boolean[128];
    Pool<KeyEvent> keyEventPool;
    List<KeyEvent> keyEventsBuffer = new ArrayList<KeyEvent>();    
    List<KeyEvent> keyEvents = new ArrayList<KeyEvent>();

The KeyboardHandler class implements the OnKeyListener interface so that it can receive key events from a View. The members are next.

The first member is an array holding 128 Booleans. We store the current state (pressed or not) of each key in this array. It is indexed by the key's key code. Luckily for us, the android.view.KeyEvent.KEYCODE_XXX constants (which encode the key codes) are all between 0 and 127, so we can store them in a garbage collector–friendly form. Note that by an unlucky accident, our KeyEvent class shares its name with the Android KeyEvent class, of which instances get passed to our OnKeyEventListener.onKeyEvent() method. This slight confusion is only limited to this handler code. As there's no better name for a key event than “KeyEvent,” we chose to live with this short-lived confusion.

The next member is a Pool that holds the instances of our KeyEvent class. We don't want to make the garbage collector angry, so we recycle all the KeyEvent objects we create.

The third member stores the KeyEvents that have not yet been consumed by our game. Each time we get a new key event on the UI thread, we add it to this list.

The last member stores the KeyEvents that we return by calling the KeyboardHandler.getKeyEvents(). In the following sections, we'll see why we have to double-buffer the key events.

    public KeyboardHandler(View view) {
        PoolObjectFactory<KeyEvent> factory = new PoolObjectFactory<KeyEvent>() {
            @Override
            public KeyEvent createObject() {
                return new KeyEvent();
            }
        };
        keyEventPool = new Pool<KeyEvent>(factory, 100);
        view.setOnKeyListener(this);
        view.setFocusableInTouchMode(true);
        view.requestFocus();
    }

The constructor has a single parameter consisting of the View from which we want to receive key events. We create the Pool instance with a proper PoolObjectFactory, register the handler as an OnKeyListener with the View, and finally, make sure that the View will receive key events by making it the focused View.

    @Override
    public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
        if (event.getAction() == android.view.KeyEvent.ACTION_MULTIPLE)
            return false;

        synchronized (this) {
            KeyEvent keyEvent = keyEventPool.newObject();
            keyEvent.keyCode = keyCode;
            keyEvent.keyChar = (char) event.getUnicodeChar();
            if (event.getAction() == android.view.KeyEvent.ACTION_DOWN) {
                keyEvent.type = KeyEvent.KEY_DOWN;
                if(keyCode > 0 && keyCode < 127)
                    pressedKeys[keyCode] = true;
            }
            if (event.getAction() == android.view.KeyEvent.ACTION_UP) {
                keyEvent.type = KeyEvent.KEY_UP;
                if(keyCode > 0 && keyCode < 127)
                    pressedKeys[keyCode] = false;
            }
            keyEventsBuffer.add(keyEvent);
        }
        return false;
    }

Next, we will discuss our implementation of the OnKeyListener.onKey() interface method, which is called each time the View receives a new key event. We start by ignoring any (Android) key events that encode a KeyEvent.ACTION_MULTIPLE event. These are not relevant in our context. This is followed by a synchronized block. Remember, the events are received on the UI thread and read on the main loop thread, so we have to make sure that none of our members are accessed in parallel.

Within the synchronized block, we first fetch a KeyEvent instance (of our KeyEvent implementation) from the Pool. This will either get us a recycled instance or a brand new one, depending on the state of the Pool. Next, we set the KeyEvent's keyCode and keyChar members based on the contents of the Android KeyEvent that were passed to the method. Then, we decode the Android KeyEvent type and set the type of our KeyEvent, as well as the element in the pressedKey array, accordingly. Finally, we add our KeyEvent to the previously defined keyEventBuffer list.

    public boolean isKeyPressed(int keyCode) {
        if (keyCode < 0 || keyCode > 127)
            return false;
        return pressedKeys[keyCode];
    }

The next method of our handler the isKeyPressed() method, which implements the semantics of Input.isKeyPressed(). First, we pass in an integer that specifies the key code (one of the Android KeyEvent.KEYCODE_XXX constants) and returns whether that key is pressed or not. We do this by looking up the state of the key in the pressedKey array after some range checking. Remember, we set the elements of this array in the previous method, which gets called on the UI thread. Since we are working with primitive types again, there's no need for synchronization.

    public List<KeyEvent> getKeyEvents() {
        synchronized (this) {
            int len = keyEvents.size();
            for (int i = 0; i < len; i++)
                keyEventPool.free(keyEvents.get(i));
            keyEvents.clear();
            keyEvents.addAll(keyEventsBuffer);
            keyEventsBuffer.clear();
            return keyEvents;
        }
    }
}

The last method of our handler is called getKeyEvents(), and it implements the semantics of the Input.getKeyEvents() method. Once again, we start with a synchronized block and remember that this method will be called from a different thread.

Next, we loop through the keyEvents array, and insert all of its KeyEvents into our Pool. Remember, we fetch instances from the Pool in the onKey() method on the UI thread. Here, we reinsert them into the Pool. But isn't the keyEvents list empty? Yes, but only the first time we invoke that method. To understand why, you have to grasp the rest of the method.

After our mysterious Pool insertion loop, we clear the keyEvents list and fill it with the events in our keyEventsBuffer list. Finally, we clear the keyEventsBuffer list and return the newly–filled keyEvents list to the caller. What is happening here?

We'll use a simple example to illustrate this. First, we'll examine what happens to the keyEvents and the keyEventsBuffer lists, as well as to our Pool each time a new event arrives on the UI thread or the game fetches the events in the main thread:

UI thread: onKey() ->
           keyEvents = { }, keyEventsBuffer = {KeyEvent1}, pool = { }
Main thread: getKeyEvents() ->
           keyEvents = {KeyEvent1}, keyEventsBuffer = { }, pool { }
UI thread: onKey() ->
           keyEvents = {KeyEvent1}, keyEventsBuffer = {KeyEvent2}, pool { }
Main thread: getKeyEvents() ->
           keyEvents = {KeyEvent2}, keyEventsBuffer = { }, pool = {KeyEvent1}
UI thread: onKey() ->
           keyEvents = {KeyEvent2}, keyEventsBuffer = {KeyEvent1}, pool = { }
  1. We get a new event in the UI thread. There's nothing in the Pool yet, so a new KeyEvent instance (KeyEvent1) is created and inserted into the keyEventsBuffer list.
  2. We call getKeyEvents() on the main thread. getKeyEvents() takes KeyEvent1 from the keyEventsBuffer list and puts it into the keyEvents list that is returns to the caller.
  3. We get another event on the UI thread. We still have nothing in the Pool, so a new KeyEvent instance (KeyEvent2) is created and inserted into the keyEventsBuffer list.
  4. The main thread calls getKeyEvents() again. Now, something interesting happens. Upon entry into the method, the keyEvents list still holds KeyEvent1. The insertion loop will place that event into our Pool. It then clears the keyEvents list and inserts any KeyEvent into the keyEventsBuffer, in this case, KeyEvent2. We just recycled a key event.
  5. Another key event arrives on the UI thread. This time, we have a free KeyEvent in our Pool, which we happily reuse. Incredibly, there's no garbage collection!

This mechanism comes with one caveat, which is that we have to call KeyboardHandler.getKeyEvents() frequently or the keyEvents list fills up quickly, and no objects are returned to the Pool. Problems can be avoided as long as we remember this.

Touch Handlers

Now it is time to consider fragmentation. In the last chapter, we revealed that multitouch is only supported on Android versions greater than 1.6. All the nice constants we used in our multitouch code (for example, MotionEvent.ACTION_POINTER_ID_MASK) are not available to us on Android 1.5 or 1.6. We can use them in our code if we set the build target of our project to an Android version that has this API; however, the application will crash on any device running Android 1.5 or 1.6. We want our games to run on all currently available Android versions, so how do we solve this problem?

We employ a simple trick. We write two handlers, one using the single-touch API in Android 1.5, and another using the multitouch API in Android 2.0 and above. This is safe as long as we don't execute the multitouch handler code on an Android device lower than version 2.0. The VM won't load the code, and it won't throw exceptions continuously. All we need to do is find out which Android version the device is running and instantiate the proper handler. You'll see how this works when we discuss the AndroidInput class. For now, let's concentrate on the two handlers.

The TouchHandler Interface

In order to use our two handler classes interchangeably, we need to define a common interface. Listing 5–9 presents the TouchHandler interface.

Listing 5–9. TouchHandler.java, to Be Implemented for Android 1.5  and 1.6.

package com.badlogic.androidgames.framework.impl;

import java.util.List;

import android.view.View.OnTouchListener;

import com.badlogic.androidgames.framework.Input.TouchEvent;

public interface TouchHandler extends OnTouchListener {
    public boolean isTouchDown(int pointer);

    public int getTouchX(int pointer);

    public int getTouchY(int pointer);

    public List<TouchEvent> getTouchEvents();
}

All TouchHandlers must implement the OnTouchListener interface, which is used to register the handler with a View. The methods of the interface correspond to the respective methods of the Input interface defined in Chapter 3. The first three are for polling the state of a specific pointer ID, and the last is for getting TouchEvents with which to perform event-based input handling. Note that the polling methods take pointer IDs that can be any number and are given by the touch event.

The SingleTouchHandler Class

In the case of our single-touch handler, we ignore any IDs other than zero. To recap, we'll recall the TouchEvent class defined in Chapter 3 as part of the Input interface.

public static class TouchEvent {
    public static final int TOUCH_DOWN = 0;
    public static final int TOUCH_UP = 1;
    public static final int TOUCH_DRAGGED = 2;

    public int type;
    public int x, y;
    public int pointer; }

Like the KeyEvent class, it defines a couple of constants that echo the touch event's type, along with the x- and y-coordinates in the coordinate system of the View and the pointer ID. Listing 5–10 shows the implementation of the TouchHandler interface for Android 1.5 and 1.6.

Listing 5–10. SingleTouchHandler.java; Good with Single Touch, Not So Good with Multitouch

package com.badlogic.androidgames.framework.impl;

import java.util.ArrayList;
import java.util.List;

import android.view.MotionEvent;
import android.view.View;

import com.badlogic.androidgames.framework.Pool;
import com.badlogic.androidgames.framework.Input.TouchEvent;
import com.badlogic.androidgames.framework.Pool.PoolObjectFactory;

public class SingleTouchHandler implements TouchHandler {
    boolean isTouched;
    int touchX;
    int touchY;
    Pool<TouchEvent> touchEventPool;
    List<TouchEvent> touchEvents = new ArrayList<TouchEvent>();
    List<TouchEvent> touchEventsBuffer = new ArrayList<TouchEvent>();
    float scaleX;
    float scaleY;

We start by letting the class implement the TouchHandler interface, which also means that we must implement the OnTouchListener interface. Next, we have three members that store the current state of the touchscreen for one finger, followed by a Pool and two lists that hold the TouchEvents. This is the same as in the KeyboardHandler. We also have two members, scaleX and scaleY. We'll address these in the following sections and use them to cope with different screen resolutions.

NOTE: Of course, we could make this more elegant by deriving the KeyboardHandler and SingleTouchHandler from a base class that handles all matters regarding pooling and synchronization. However, it would have complicated the explanation even more, so instead, we'll write a few more lines of code.

public SingleTouchHandler(View view, float scaleX, float scaleY) {
        PoolObjectFactory<TouchEvent> factory = new PoolObjectFactory<TouchEvent>() {
            @Override
            public TouchEvent createObject() {
                return new TouchEvent();
            }
        };
        touchEventPool = new Pool<TouchEvent>(factory, 100);
        view.setOnTouchListener(this);

        this.scaleX = scaleX;
        this.scaleY = scaleY;
    }

In the constructor, we register the handler as an OnTouchListener and set up the Pool that we use to recycle TouchEvents. We also store the scaleX and scaleY parameters that are passed to the constructor (ignore them for now).

@Override
    public boolean onTouch(View v, MotionEvent event) {
        synchronized(this) {
            TouchEvent touchEvent = touchEventPool.newObject();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchEvent.type = TouchEvent.TOUCH_DOWN;
                isTouched = true;
                break;
            case MotionEvent.ACTION_MOVE:
                touchEvent.type = TouchEvent.TOUCH_DRAGGED;
                isTouched = true;
                break;
            case MotionEvent.ACTION_CANCEL:            
            case MotionEvent.ACTION_UP:
                touchEvent.type = TouchEvent.TOUCH_UP;
                isTouched = false;
                break;
            }

            touchEvent.x = touchX = (int)(event.getX() * scaleX);
            touchEvent.y = touchY = (int)(event.getY() * scaleY);
            touchEventsBuffer.add(touchEvent);                    

            return true;
        }
    }

The onTouch() method achieves the same outcome as our KeyboardHandler's onKey() method; the only difference is that now we handle TouchEvents instead of KeyEvents. All the synchronization, pooling, and MotionEvent handling are already known to us. The only interesting thing is that we multiply the reported x- and y-coordinates of a touch event by scaleX and scaleY. This is important to remember as we'll return to it in the following sections.

@Override
    public boolean isTouchDown(int pointer) {
        synchronized(this) {
            if(pointer == 0)
                return isTouched;
            else
                return false;
        }
    }

    @Override
    public int getTouchX(int pointer) {
        synchronized(this) {
            return touchX;
        }
    }

    @Override
    public int getTouchY(int pointer) {
        synchronized(this) {
            return touchY;
        }
    }

The methods isTouchDown(), getTouchX(), and getTouchY() allow us to poll the state of the touchscreen based on the members that we set in the onTouch() method. The only noticeable thing about them is that they only return useful data for a pointer ID with a value of zero, since this class only supports single-touch screens.

@Override
    public List<TouchEvent> getTouchEvents() {
        synchronized(this) {    
            int len = touchEvents.size();
            for( int i = 0; i < len; i++ )
                touchEventPool.free(touchEvents.get(i));
            touchEvents.clear();
            touchEvents.addAll(touchEventsBuffer);
            touchEventsBuffer.clear();
            return touchEvents;
        }
    }
}

The final method, SingleTouchHandler.getTouchEvents(), should be familiar to you, and is similar to the KeyboardHandler.getKeyEvents() methods. Remember that we call this method frequently the touchEvents list doesn't fill up.

The MultiTouchHandler

For multitouch handling, we use a class called MultiTouchHandler, as shown in Listing 5–11.

Listing 5–11. MultiTouchHandler.java (More of the Same)

package com.badlogic.androidgames.framework.impl;

import java.util.ArrayList;
import java.util.List;

import android.view.MotionEvent;
import android.view.View;

import com.badlogic.androidgames.framework.Input.TouchEvent;
import com.badlogic.androidgames.framework.Pool;
import com.badlogic.androidgames.framework.Pool.PoolObjectFactory;

public class MultiTouchHandler implements TouchHandler {
    private static final int MAX_TOUCHPOINTS = 10;

    boolean[] isTouched = new boolean[MAX_TOUCHPOINTS];
    int[] touchX = new int[MAX_TOUCHPOINTS];
    int[] touchY = new int[MAX_TOUCHPOINTS];
    int[] id = new int[MAX_TOUCHPOINTS];
    Pool<TouchEvent> touchEventPool;
    List<TouchEvent> touchEvents = new ArrayList<TouchEvent>();
    List<TouchEvent> touchEventsBuffer = new ArrayList<TouchEvent>();
    float scaleX;
    float scaleY;

    public MultiTouchHandler(View view, float scaleX, float scaleY) {
        PoolObjectFactory<TouchEvent> factory = new PoolObjectFactory<TouchEvent>() {
            @Override
            public TouchEvent createObject() {
                return new TouchEvent();
            }
        };
        touchEventPool = new Pool<TouchEvent>(factory, 100);
        view.setOnTouchListener(this);

        this.scaleX = scaleX;
        this.scaleY = scaleY;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        synchronized (this) {
            int action = event.getAction() & MotionEvent.ACTION_MASK;
            int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK)
>> MotionEvent.ACTION_POINTER_ID_SHIFT;
            int pointerCount = event.getPointerCount();
            TouchEvent touchEvent;
            for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
                if (i >= pointerCount) {
                    isTouched[i] = false;
                    id[i] = -1;
                    continue;
                }
                int pointerId = event.getPointerId(i);
                if (event.getAction() != MotionEvent.ACTION_MOVE && i != pointerIndex) {
                    // if it's an up/down/cancel/out event, mask the id to see if we should process it for this touch
                    // point
                    continue;
                }
                switch (action) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_POINTER_DOWN:
                    touchEvent = touchEventPool.newObject();
                    touchEvent.type = TouchEvent.TOUCH_DOWN;
                    touchEvent.pointer = pointerId;
                    touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
                    touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
                    isTouched[i] = true;
                    id[i] = pointerId;
                    touchEventsBuffer.add(touchEvent);
                    break;

                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                case MotionEvent.ACTION_CANCEL:
                    touchEvent = touchEventPool.newObject();
                    touchEvent.type = TouchEvent.TOUCH_UP;
                    touchEvent.pointer = pointerId;
                    touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
                    touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
                    isTouched[i] = false;
                    id[i] = -1;
                    touchEventsBuffer.add(touchEvent);
                    break;

                case MotionEvent.ACTION_MOVE:
                    touchEvent = touchEventPool.newObject();
                    touchEvent.type = TouchEvent.TOUCH_DRAGGED;
                    touchEvent.pointer = pointerId;
                    touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
                    touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
                    isTouched[i] = true;
                    id[i] = pointerId;
                    touchEventsBuffer.add(touchEvent);
                    break;
                }
            }
            return true;
        }
    }

    @Override
    public boolean isTouchDown(int pointer) {
        synchronized (this) {
            int index = getIndex(pointer);
            if (index < 0 || index >= MAX_TOUCHPOINTS)
                return false;
            else
                return isTouched[index];
        }
    }

    @Override
    public int getTouchX(int pointer) {
        synchronized (this) {
            int index = getIndex(pointer);
            if (index < 0 || index >= MAX_TOUCHPOINTS)
                return 0;
            else
                return touchX[index];
        }
    }

    @Override
    public int getTouchY(int pointer) {
        synchronized (this) {
            int index = getIndex(pointer);
            if (index < 0 || index >= MAX_TOUCHPOINTS)
                return 0;
            else
                return touchY[index];
        }
    }

    @Override
    public List<TouchEvent> getTouchEvents() {
        synchronized (this) {
            int len = touchEvents.size();
            for (int i = 0; i < len; i++)
                touchEventPool.free(touchEvents.get(i));
            touchEvents.clear();
            touchEvents.addAll(touchEventsBuffer);
            touchEventsBuffer.clear();
            return touchEvents;
        }
    }

    // returns the index for a given pointerId or -1 if no index.
    private int getIndex(int pointerId) {
        for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
            if (id[i] == pointerId) {
                return i;
            }
        }
        return -1;
    }
}

The onTouch() method looks as intimidating as our test example in the last chapter. However, all we need to do is marry that test code with our event pooling and synchronization, which we've already talked about in detail. The only real difference from the SingleTouchHandler.onTouch() method is that we handle multiple pointers and set the TouchEvent.pointer member accordingly (instead of using a value of zero).

The polling methods, isTouchDown(), getTouchX(), and getTouchY( )should look familiar as well. We perform some error–checking and then fetch the corresponding pointer state for the corresponding pointer index from one of the member arrays that we fill in the onTouch() method.

@Override
    public List<TouchEvent> getTouchEvents() {
        synchronized (this) {
            int len = touchEvents.size();
            for (int i = 0; i < len; i++)
                touchEventPool.free(touchEvents.get(i));
            touchEvents.clear();
            touchEvents.addAll(touchEventsBuffer);
            touchEventsBuffer.clear();
            return touchEvents;
        }
    }
}

The final method, getTouchEvents(), is exactly the same as the corresponding method in SingleTouchHandler.getTouchEvents(). Now that we are equipped with all these handlers, we can implement the Input interface.

AndroidInput: The Great Coordinator

The Input implementation of our game framework ties together all the handlers we have developed. Any method calls are delegated to the corresponding handler. The only interesting part of this implementation is choosing which TouchHandler implementation to use, based on the Android version the device is running. Listing 5–12 shows you an implementation called AndroidInput.

Listing 5–12. AndroidInput.java; Handling the Handlers with Style

package com.badlogic.androidgames.framework.impl;

import java.util.List;

import android.content.Context;
import android.os.Build.VERSION;
import android.view.View;

import com.badlogic.androidgames.framework.Input;

public class AndroidInput implements Input {
    AccelerometerHandler accelHandler;
    KeyboardHandler keyHandler;
    TouchHandler touchHandler;

We start by letting the class implement the Input interface defined in Chapter 3. This leads us to three members: an AccelerometerHandler, a KeyboardHandler, and a TouchHandler.

public AndroidInput(Context context, View view, float scaleX, float scaleY) {
        accelHandler = new AccelerometerHandler(context);
        keyHandler = new KeyboardHandler(view);
        if (Integer.parseInt(VERSION.SDK) < 5)
            touchHandler = new SingleTouchHandler(view, scaleX, scaleY);
        else
            touchHandler = new MultiTouchHandler(view, scaleX, scaleY);
    }

These members are initialized in the constructor, which takes a Context, a View, and the scaleX and scaleY parameters, which we can ignore again. The AccelerometerHandler is instantiated via the Context parameter, as the KeyboardHandler needs the View that is passed in.

To decide which TouchHandler to use, we simply check the Android version that the application uses to run. This can be done using the VERSION.SDK string, which is a constant provided by the Android API. It is unclear why this is a string since it directly encodes the SDK version numbers we use in our manifest file. Therefore, we need to make it into an integer in order to do some comparisons. The first Android version to support the multitouch API was version 2.0, which corresponds to SDK version 5. If the current device runs a lower Android version, we instantiate the SingleTouchHandler; otherwise, we use the MultiTouchHandler. At an API level, this is all the fragmentation we need to care about. When we start rendering OpenGL, we'll hit a few more fragmentation issues, but there is no need to worry–they are easily resolved, just like the touch API problems.

    @Override
    public boolean isKeyPressed(int keyCode) {
        return keyHandler.isKeyPressed(keyCode);
    }

    @Override
    public boolean isTouchDown(int pointer) {
        return touchHandler.isTouchDown(pointer);
    }

    @Override
    public int getTouchX(int pointer) {
        return touchHandler.getTouchX(pointer);
    }

    @Override
    public int getTouchY(int pointer) {
        return touchHandler.getTouchY(pointer);
    }

    @Override
    public float getAccelX() {
        return accelHandler.getAccelX();
    }

    @Override
    public float getAccelY() {
        return accelHandler.getAccelY();
    }

    @Override
    public float getAccelZ() {
        return accelHandler.getAccelZ();
    }

    @Override
    public List<TouchEvent> getTouchEvents() {
        return touchHandler.getTouchEvents();
    }

    @Override
    public List<KeyEvent> getKeyEvents() {
        return keyHandler.getKeyEvents();
    }
}

The rest of this class is self-explanatory. Each method call is delegated to the appropriate handler, which does the actual work. With this, we have finished the input API of our game framework. Next, we'll discuss graphics.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.33.158