CHAPTER SEVEN: Touches and Swipes

Chapter opener image: © Fon_nongkran/Shutterstock

Introduction

Many mobile games use screen touching, tapping, or swiping as a way to interact with the user. In most map apps, the user can zoom in and out of the map by doing some gesture such as pinching or spreading his or her fingers. All these things are events and follow the same rules as event handling in general:

  • ▸ There is an event handling class, typically implementing an interface.

  • ▸ We create an object of the class, a listener.

  • ▸ We register that listener on one or more Graphical User Interface (GUI) components.

In this chapter, we learn how to detect and handle touches, swipes, and taps. We also build a simple puzzle app that lets the user move a piece of the puzzle by touching it and dragging it to another position in the puzzle. FIGURE 7.1 shows the puzzle, which uses five TextViews whose order has been scrambled and need to be placed in the correct order.

FIGURE 7.1 The Puzzle app, Version 2, running inside the emulator

7.1 Detecting a Touch Event

Before working on the puzzle app, we build a simple practice app containing one TextView. When the user touches it, we move the TextView along as the user moves his or her finger. We use the Empty Activity template for this app. The View.OnTouchListener interface, a public static inner interface of the View class, provides a method, onTouch, that is called when the user interacts with the screen. A method that is called when something happens, for example an event, is called a Callback method. TABLE 7.1 shows that method.

In order to handle a touch event using the View.OnTouchListener interface, we need to:

  • ▸ Define a class that implements View.OnTouchListener and overrides the onTouch method.

  • ▸ Create an object of that class.

  • ▸ Register that object on one or more Views.

We can define that class as a private class of the current activity class:

// private class inside the MainActivity class
private class TouchHandler implements View.OnTouchListener {
  // the TouchHandler class needs to override onTouch
  public boolean onTouch( View v, MotionEvent event ) {
    // process the event here
  }
}

In order to register an object of type View.OnTouchListener on a View, we use the setOnTouchListener method of the View class, shown in TABLE 7.2.

// Inside MainActivity, if we want the View v to respond to touch events
TouchHandler th = new TouchHandler( );
v.setOnTouchListener( th );

An alternative to defining a new class implementing View.OnTouchListener is for the current Activity class to implement View.OnTouchListener. Then, this is a View.OnTouchListener object and we can register it on one or more Views. If the Activity class is MainActivity, we would write:

// MainActivity implements View.OnTouchListener
public class MainActivity extends Activity implements
             View.OnTouchListener {

// if we want the View v to respond to touch events
v.setOnTouchListener( this );

// the MainActivity class needs to override onTouch
public boolean onTouch( View v, MotionEvent event ) {
  // process the event here

}

TABLE 7.1 The View.OnTouchListener interface

Method Description
boolean onTouch( View v, MotionEvent event ) Called when a touch event occurs. v is the View where the event occurred (assuming that this listener is registered on v). If this method returns true, the event is consumed. If it returns false, the event is propagated to the Views that are underneath v in the View stack.

TABLE 7.2 The setOnTouchListener method of the View class

Method Description
void setOnTouchListener( View.OnTouchListener listener ) Registers listener on this View. When a touch event is sent to this View, the onTouch method of listener will be called.

The first parameter of the onTouch method is a View where the event occurred, assuming that the listener is registered on that View. The second parameter is a MotionEvent reference. It contains information about the event. When the user touches the screen, moves his or her finger, and then lifts it up, there is actually a series of events that happen in sequence. The onTouch method is called many times, each time with a different value for the event parameter. This allows us to test that event parameter and process the event accordingly. The MotionEvent parameter event contains information about the current event within the sequence of events. It can even include information about how much time has passed during the whole sequence, how hard the touch on the screen was, etc. The method getAction, listed with other methods of the MotionEvent class in TABLE 7.3, returns an integer value that identifies the type of action that occurred. We can compare that value to one of the action constants of the MotionEvent class. Some of them are listed in TABLE 7.4. Thus, inside onTouch, we can call getAction with event, compare the result to a constant of the MotionEvent class that corresponds to an event we are interested in, and process the result accordingly. A good way to do that is with a switch statement as follows (replacing SOME_ACTION and SOME_OTHER_ACTION with actual constants like ACTION_DOWN, ACTION_UP, etc.):

public boolean onTouch( View v, MotionEvent event ) {
  int action = event.getAction( );
  switch( action ) {
  case MotionEvent.SOME_ACTION:
    // some action happened; process that information
    break;
  case MotionEvent.SOME_OTHER_ACTION:
    // some other action happened; process that information
    break;
   ...
 }
 ...
}

TABLE 7.3 Selected methods of the MotionEvent class

Method Description
float getRawX( ) Returns the x-coordinate of the touch within the screen.
float getRawY( ) Returns the y-coordinate of the touch within the screen.
float getX( ) Returns the x-coordinate of the touch within the View where it happened.
float getY( ) Returns the y-coordinate of the touch within the View where it happened.
int getAction( ) Returns the type of action that occurred within the touch event.

TABLE 7.4 Selected constants of the MotionEvent class that can be compared to the return value of the getAction method

Constant Description
ACTION_DOWN The user touched the screen.
ACTION_UP The user stopped touching the screen.
ACTION_MOVE The user is moving his or her finger on the screen.

We concentrate on the first touch (ACTION_DOWN constant), the dragging of the finger (ACTION_MOVE constant), and the lifting of the finger (ACTION_UP constant). In this first example, we simply output to Logcat some information about what action is being processed, for what event, and on what View. EXAMPLE 7.1 shows the MainActivity class.

We declare that MainActivity implements the View.OnTouchListener interface at lines 9–10. Inside onCreate, we retrieve the content view for the activity at line 17 using the default id content, which identifies it by default. We output it at line 18, so that we can compare its value to the value of the View parameter of the onTouch method. At line 19, we register this, a View.OnTouchListener, on that content View. In this way, whenever the user interacts with the screen via touches, the onTouch method executes and its View parameter is the content View.

EXAMPLE 7.1 The MainActivity class, tracking touch events, Touches app, Version 0

Inside the onTouch method (at lines 22–36), we first retrieve the action performed at line 23. We compare its value to the ACTION_DOWN, ACTION_MOVE, and ACTION_UP constants of the MotionEvent class using a switch construct. For each case, we output to Logcat information about v, the View parameter, and event, the MotionEvent parameter.

FIGURE 7.2 Logcat output from Example 7.1

FIGURE 7.2 shows the output in Logcat from Example 7.1 as the user makes a small swipe somewhere on the screen. We can observe several things:

  • ▸ The method is called first with a DOWN action, then many times with a MOVE action, then once with an UP action.

  • ▸ The View parameter v is always the same and it is the content view for the activity.

  • ▸ The parameter event contains information such as the action, x- and y-coordinates, time, etc. We can see that each time the method is called on a MOVE action, the x- and y-coordinate values are different. Together, they form a discrete set of values that mirrors the swipe. Note that we do not get continuous, pixel by pixel information on the swipe.

7.2 Handling a Swipe Event: Moving a TextView

In the first example, we register the touch listener on the content View. In this second example, we add a TextView to the screen and we move it as the user touches it and moves his or her finger on the screen. This time, we register the listener on the TextView. This illustrates the difference between the getX and getY methods on one hand, and the getRawX and getRawY methods on the other hand, shown in Table 7.3. The getX and getY methods return the x- and y-coordinates relative to the View where the event happened, which is the View v passed to onTouch as the first parameter. The getRawX and getRawY return the x- and y-coordinates within the screen, including the screen decorations such as the status bar and the action bar. If we use the getRawX and getRawY methods and we want the x- and y-coordinates relative to the content View, we can use the following code inside onTouch to convert them:

int [] location = new int[2];
// view represents the content view for the activity
view.getLocationInWindow( location );
float relativeX = event.getRawX( ) - location[0];
float relativeY = event.getRawY( ) - location[1];

The getLocationInWindow method of the View class accepts an array of two ints as a parameter, and sets its two values to the x- and y-coordinates of the top left corner of the View calling that method within the window. We can then subtract their values from the values returned by getRawX and getRawY to obtain the relative x- and y-coordinates of the touch event within the content view of the activity.

By contrast, if the listener has been registered on a TextView and the first touch is at the top left corner of the TextView, then the View v parameter of the onTouch method is that TextView, and the getX and getY method return 0 and 0 (or close to it) independently of where the TextView is located on the screen.

EXAMPLE 7.2 shows the MainActivity class. We build the GUI by code and include one TextView in it. We want to move that TextView based on user touches, so we need to use absolute coordinates. The AbsoluteLayout class, originally intended to work with absolute coordinates, is deprecated. We use the RelativeLayout class instead. A RelativeLayout allows us to position components either relative to each other or using absolute coordinates. Each component’s dimension and position can be defined and controlled inside that RelativeLayout by associating it with it a RelativeLayout.LayoutParams object when we add it to the RelativeLayout. The TextView instance variable, tv, is declared at line 12, along with params, its RelativeLayout.LayoutParams parameters at line 13. By declaring them as instance variables, we can access them inside the onTouch method.

EXAMPLE 7.2 The MainActivity class, moving a TextView with a swipe, Touches app, Version 1

We build the GUI by code inside the buildGuiByCode method (lines 24–37), called at line 21. At line 25, we instantiate tv and we give it a red background at line 26. At line 28, we create a RelativeLayout and we set the content view with it at line 34. At lines 29–31, we define params, and use it to add tv to the relative layout at line 33. Inside the onTouch method, when we modify params and reset the layout parameters of tv with params, we automatically modify the position of tv. RelativeLayout.LayoutParams is a subclass of ViewGroup.MarginLayoutParams and inherits its margin-related fields, shown in TABLE 7.5.

We add more instance variables at lines 14–17 to keep track of the original position of the TextView and the location of the touch when the swipe starts. We use them inside the onTouch methods to recalculate the left corner position of the TextView as the user moves his or her finger on the screen. Inside the onTouch method (lines 39–55), we initialize these four variables when the user first touches the screen (lines 43–46), which is captured by the DOWN action (line 42). When the user moves his or her finger (MOVE action, line 48), we update the leftMargin and topMargin parameters of params at lines 49–50 and update the position of tv accordingly at line 51 by calling the setLayoutParams method, shown in TABLE 7.6, passing the updated value of params. There is nothing to do when the user lifts up his or her finger from the screen, so we do not account for the UP action in our code.

TABLE 7.5 The public fields of the ViewGroup.MarginLayoutParams class

Method Description
int bottomMargin The bottom margin in pixels of the child
int leftMargin The left margin in pixels of the child
int rightMargin The right margin in pixels of the child
int topMargin The top margin in pixels of the child

TABLE 7.6 The setLayoutParams method of the View class

Method Description
void setLayoutParams( ViewGroup.LayoutParams params ) Sets the layout parameters associated with this View

Note that in this example, we register the listener on tv at line 36, and not on the content view. Thus, the calls to onTouch are triggered only when the touch starts inside tv, but no call to onTouch is triggered when the touch starts outside tv.

As we run this practice app, if we touch the red rectangle and move our finger, the red rectangle moves along.

7.3 The Model

Now that we understand how touch events work and how to move a TextView, we can start building our Puzzle app. As shown in Figure 7.1, it presents the user with several TextViews that need to be reordered. The first step is to design our Model, the Puzzle class, which includes the following:

  • ▸ An array of Strings, parts, storing the puzzle in the correct order.

  • ▸ A method returning an array of Strings storing the puzzle in a random incorrect order.

  • ▸ A method checking if an array of Strings is in the correct order.

  • ▸ A method returning the number of pieces in the puzzle (i.e., the number of elements in parts).

EXAMPLE 7.3 shows the Puzzle class. To keep things simple, we hard code the contents of the parts array in the default constructor (lines 10–17). We could envision a more complex model, with a constructor reading a puzzle data from a file or a database. In this case, we would not know the number of pieces in the puzzle before we read the data. For this reason, we provide an accessor for the number of pieces in the puzzle, getNumberOfParts, at lines 47–49. In this way, whenever the Controller needs to know how many pieces are in the puzzle, it can call that method, rather than use the constant NUMBER_PARTS, whose value is hard coded. We could also have many puzzles stored in a file or a database and read one at random. In this example, we can change the number of pieces of the puzzle inside the Model and the app would still work. For example, we can delete lines 15 and 16 and change the value of NUMBER_PARTS to 3, and the app would still work properly.

The solved method, at lines 19–29, returns true if its parameter array has the same content as parts, false otherwise. The scramble method, at lines 31–45, returns an array of Strings containing all the elements of the puzzle but in a different order. To do this, we first create a new array, scrambled, at line 32, and initialize it with the elements of parts at lines 33–34. At lines 36–43, we use a while loop to shuffle the elements of scrambled in place until the scrambled elements are in a different order as compared to the parts elements. To test for that, we use the solved method (line 36).

EXAMPLE 7.3 The Puzzle class, Puzzle app, Version 0

The for loop at lines 37–42 shuffle the elements of scrambled in place going from left to right, starting at index 0. At each iteration of the loop, the elements between indexes 0 and i – 1 have already been shuffled. The current iteration of the loop swaps the element at index i with an element at a random index between i and scrambled.length – 1. At line 38, we generate a random index between i and scrambled.length – 1 included. Then, we swap the element at that index with the element at index i (lines 39–41).

7.4 The View: Setting Up the GUI, Puzzle App, Version 0

Now that we understand touch events and we have a Model, we can start building a functional puzzle app. In this section, we build the View part of the app. To keep things simple, we only allow the app to display in vertical orientation. in the AndroidManifest.xml file, we specify this as follows:

<activity
android:screenOrientation=”portrait”

Inside the styles.xml file, we set the font size for TextViews to be 32 as follows:

<item name=”android:textSize”>32sp</item>

Each piece of the puzzle is displayed in a TextView. Although the Puzzle’s default constructor specifies a puzzle with five pieces, we could easily envision having another constructor that lets the user specify the number of pieces, and then retrieves the Strings from a file or a database. Thus, we define our View by code rather than with an XML file so that it can accommodate a variable number of TextViews. Furthermore, we place the View in a separate class, different from the MainActivity class, which represents the Controller.

EXAMPLE 7.4 shows the PuzzleView class. Rather than extend View and manage the View with a RelativeLayout inside, we extend RelativeLayout instead. Since RelativeLayout inherits from View, it is a View and can be assigned to an Activity as its View. We use the RelativeLayout class to organize the TextViews because it can manage its children Views using absolute coordinates.

We code a constructor that takes four parameters as follows:

  • activity, an Activity reference—we will instantiate a PuzzleView from an Activity class.

  • width, an int, representing the width of the screen.

  • height, an int, representing the height of the screen.

  • numberOfPieces, an int, representing the number of pieces in the puzzle.

We build the entire GUI by code using the buildGuiByCode method, coded at lines 23–41, and called from the PuzzleView constructor at line 20, passing all the constructor’s parameters to buildGuiByCode. Each component’s dimension and position can be defined and controlled by associating with it a RelativeLayout.LayoutParams object when we add it to the RelativeLayout. We declare the following instance variables:

EXAMPLE 7.4 The PuzzleView class, Puzzle app, Version 0

  • tvs, an array of TextViews (line 11). There is one TextView for each puzzle piece.

  • params, an array of RelativeLayout.LayoutParams (line 12).

  • colors, an array of integer values representing colors (line 13). We generate them randomly in order to color the TextViews.

  • labelHeight, the height of each TextView (line 15).

The three arrays above are parallel arrays. We use the params array to position and size the TextViews in tvs, and the colors array to color them. They are instantiated at lines 25–27. At lines 30–40, we loop through the elements in the array tvs. Each TextView is colored, positioned, and sized using the corresponding values in the three arrays above. At lines 33–34, we generate three random integers between 0 and 255 included and pass them to the rgb static method of the Color class in order to generate a random color for the current TextView. Note that we are using the Color class from the android.graphics package (imported at line 8), not the Color class from the traditional java.awt package.

We want all the TextViews to have the same height, which we store in labelHeight. At line 29, we assign labelHeight the height parameter (representing the height of the screen), divided by numberOfPieces. At line 36, we instantiate each element of params using width and labelHeight. At lines 37–38, we set the coordinates of the left upper corner of each element of params. At line 39, each TextView is added to this PuzzleView using the addView method of the ViewGroup class (the direct superclass of RelativeLayout) that accepts a ViewGroup.LayoutParams (a superclass of RelativeLayout.LayoutParams) object as its second parameter.

The fillGui method, coded at lines 43–46, provides code to fill the TextViews with an array of Strings representing the scrambled pieces of the puzzle.

EXAMPLE 7.5 shows the MainActivity class. It includes two instance variables: puzzleView, a PuzzleView reference representing the View (line 13), and puzzle, a Puzzle reference representing the Model (line 14).

The onCreate method (lines 16–49) instantiates puzzle at line 18 and puzzleView at lines 43–44. It sets the content View of this Activity to puzzleView at line 48. We retrieve the height and width of the screen at lines 20–23. While the width of the screen is also the width of the puzzle, the height of the screen includes the height of the status bar and the action bar, which should not be included in the puzzle. At the time of this writing, and according to Google’s design guidelines, the height of the status bar is 24 dp and the height of the action bar is 56 dp. Thus, we declare two constants defining default values for the status bar and action bar heights at lines 11–12.

We start by assigning these default values multiplied by the pixel density to the actionBarHeight and statusBarHeight variables at lines 29 and 36 before attempting to retrieve them programmatically. We retrieve the pixel density of the current device at lines 25–27 by first retrieving a Resources reference, then retrieving a DisplayMetrics reference for the current device, and then accessing its density field. The DisplayMetrics class contains information about the display, including its size, its density, and font scaling. There is no guarantee that the height of the action bar will not change in the future. Thus, it is better to retrieve it programmatically. At lines 30–34, we attempt to retrieve the height of the action bar and assign it to actionBarHeight if we can. At lines 37–40, we attempt to retrieve the height of the status bar and assign it to statusBarHeight if we can. Appendix A provides a detailed explanation of this.

EXAMPLE 7.5 The MainActivity class, Puzzle app, Version 0

At line 42, we subtract the heights of the status bar and the action bar from the height of the screen in order to compute the height of the puzzle.

We call the scramble method of Puzzle at line 45 in order to generate a scrambled array of Strings for our puzzle, and then pass it to the fillGui method of PuzzleView at line 46. From a Model-View-Controller perspective, the Controller asks the Model to provide an array of scrambled Strings at line 45, and updates the View with it at line 46.

At this point, there is no event handling so the user cannot move any piece of the puzzle, but the app runs and shows the unsolved puzzle, as shown in Figure 7.1.

7.5 Moving the Pieces, Puzzle App, Version 1

We now start building the Controller part of the app, in order to give the app its functionality. In Version 1, we enable the user to move the various pieces of the puzzle (i.e., the TextViews). At this point, we do not care if the pieces are well positioned and whether the puzzle has been solved.

FIGURE 7.3 The red View is higher than the blue View in the stacking order. It partially hides the blue View

Whenever the user is moving a piece of the puzzle, we bring that piece on top of the others so the user can see it at all times. Views are arranged in a stack using a stacking order. The first View added to a ViewGroup is at the bottom of the stack and the last View added is at the top of the stack. FIGURE 7.3 illustrates the stacking order concept. The red View, which was added after the blue View, is higher in the stacking order and partially hides the blue View. TABLE 7.7 shows the bringToFront method of the View class, which we can use to bring a View to the top of the stack so that it is not hidden by its sibling Views. To force a View named view to be at the top of the stack, we call the bringToFront method with it as in the following statement:

view.bringToFront( );

In order to implement the preceding functionality, the MainActivity class implements the View.OnTouchListener interface, and overrides the onTouch method. At this point, the pseudo-code of the onTouch method is:

If the action is DOWN
  Store the y position of the piece of the puzzle touched
  Store the y position of the touch
  Bring the piece of the puzzle at the top of the stacking order
If the action is MOVE
  Move the piece of the puzzle as the user moves his or her finger

TABLE 7.7 The bringToFront method of the View class

Method Description
void bringToFront( ) Brings this View to the top of the stack so that it is on top of all its siblings.

All of the preceding involves managing the TextViews in the PuzzleView class. Thus, we provide methods in the PuzzleView class to perform these actions, and call these methods from the MainActivity class. EXAMPLE 7.6 shows the MainActivity class. It implements View.OnTouchListener (lines 12–13) and overrides the onTouch method at lines 54–70. At line 50, inside the onCreate method, we call the enableListener method of the PuzzleView class with puzzleView. That method registers this listener, MainActivity, on all the TextViews.

Inside onTouch, we first call indexOfTextView to retrieve the index of the TextView that is the target of the touch event at line 55 and the type of action within the touch event at line 56. At this point, we only care about the DOWN and MOVE actions. We do not care about the UP action yet. If it is a DOWN action (line 58), the user is picking up a piece of the puzzle and is getting ready to move it. We call updateStartPositions with puzzleView (lines 59–60) in order to update the y position of the touched TextView and the y position of the touch (we need these two y values to calculate the new location of the TextView if the action is MOVE). We bring the touched TextView to the front at lines 61–62.

EXAMPLE 7.6 The MainActivity class, Puzzle app, Version 1

When the user moves his or her finger (move touch action, line 64), we call moveTextViewVertically with puzzleView (lines 65–66) in order to move the touched TextView, following the user’s finger along the vertical axis.

EXAMPLE 7.7 shows the updated PuzzleView class. It includes all the methods called by the PuzzleView instance variable in the MainActivity class: indexOfTextView, tvPosition, enableListener, updateStartPositions, and moveTextViewVertically.

The enableListener method (lines 73–76) expects a View.OnTouchListener parameter and registers it on all the TextViews. When we call it from MainActivity, we pass this, which “is a” View.OnTouchListener reference since MainActivity implements View.OnTouchListener.

EXAMPLE 7.7 The PuzzleView class, Puzzle app, Version 1

FIGURE 7.4 The Puzzle app, Version 1, after the user moved a few pieces

In order to help us manage the dragging of the TextViews, we add the following instance variables:

  • ▸ An integer instance variable, startY (line 17), storing the y-coordinate of the top of the piece of the puzzle that we are moving.

  • ▸ An integer instance variable, startTouchY (line 18), storing the y-coordinate of the initial touch.

The indexOfTextView method, coded at lines 51–60, returns the index of its View parameter, tv, within the array tvs. We expect tv to be a TextView, but we test for it at line 53. We loop through the elements of tvs and compare the element at index i to tv (line 56) and return i if they are equal (line 57). If tv is not a TextView or we do not find it, we return –1 (lines 54 and 59).

The updateStartPositions method, coded at lines 62–65, is called on a touch action DOWN. We update the values of the instance variables startY and startTouchY at lines 63–64; startY stores the original y-coordinate of the TextView for that piece, and startTouchY stores the y-coordinate of the touch within the TextView. We need both of them to update the y-coordinate of the TextView on a MOVE touch action.

The moveTextViewVertically method, coded at lines 67–71, updates the layout parameters of the TextView at index index, its first parameter, based on the value of y, its second parameter, which represents the y-coordinate of a touch. By changing the layout parameters of a TextView, it automatically moves it to a new location defined by these layout parameters.

FIGURE 7.4 shows the puzzle after the user has moved a few pieces.

7.6 Solving the Puzzle, Puzzle App, Version 2

We now continue to build the Controller part of the app, in order to give the app its functionality. When the user moves a piece of the puzzle, a TextView, and releases it, we swap its position with the piece of the puzzle that is under it. We consider that a piece of the puzzle is over another one if at least half of the piece of the puzzle is over the other one, as shown in FIGURE 7.5. The green piece is more than halfway over the blue piece. If the user releases it, the green piece will take the blue piece’s place, and the blue piece will be placed where the green piece was when the touch event started. After each move, we check if the user has solved the puzzle. If the user has, we disable the touch events.

FIGURE 7.5 The green piece is closer to the blue piece than the red piece

In addition to processing the DOWN and MOVE actions, we also need to process the UP action in order to swap the two pieces of the puzzle. Inside MainActivity, the pseudo-code of the onTouch method is now:

If the action is DOWN
  Store the y position of the piece of the puzzle touched

  Store the y position of the touch
  Store the position of the ”empty” puzzle slot
  Bring the piece of the puzzle at the top of the stacking order
If the action is MOVE
  Move the piece of the puzzle as the user moves his or her finger
If the action is UP
  Swap the piece of the puzzle with the piece of the puzzle that is
  under it
  Check if the puzzle is solved; if it is, disable the listener

In order to implement the added functionality, we provide additional methods in the PuzzleView class, and call these methods from the MainActivity class. EXAMPLE 7.8 shows the updated onTouch method of the MainActivity class.

EXAMPLE 7.8 The updated onTouch method of the MainActivity class, Puzzle app, Version 2

When the user lifts up his or her finger (up touch action, line 68), we swap the touched TextView with the TextView currently underneath the touch: we first call tvPosition with puzzleView at line 70 in order to retrieve the new position index of the touched TextView, and then call placeTextViewAtPosition at line 71 with puzzleView in order to swap the two TextViews. Finally, we check if the user solved the puzzle at line 73. From a Model-View-Controller perspective, the Controller retrieves the state of the puzzle from the View, and asks the Model to check if this is the correct ordering of the puzzle. If it is, the Controller asks the View to disable touch event listening by calling disableListener at line 74 in order to disable touch listening. In this way, we prevent the user from moving the TextViews.

EXAMPLE 7.9 shows the updated PuzzleView class. It includes all the following additional methods called by the PuzzleView instance variable in the MainActivity class: tvPosition, placeTextViewAtPosition, disableListener, and currentSolution. We also update the buildGuiByCode, fillGui, and updateStartPositions methods as needed.

EXAMPLE 7.9 The PuzzleView class, Puzzle app, Version 2

The disableListener method (lines 84–87) sets the listener of all the TextViews to null, effectively disabling touch listening.

In order to help us manage the swapping of the TextViews, we add the following instance variables:

  • ▸ An integer instance variable, emptyPosition (line 19), storing the y position of the “empty position”, where the TextView the user is currently moving was before the move.

  • ▸ An integer-array instance variable, positions (line 20), storing the y positions of all the elements in tvs.

As the user picks up or releases a piece of the puzzle, we need to evaluate its position on the screen. FIGURES 7.6 and 7.7 show a possible starting puzzle situation and a second puzzle situation after the user switched the two bottom pieces of the puzzle. Figure 7.7 shows the difference between the position of a TextView (listed in the right column) and its index (within the array tvs, listed in the left column): tvs[4] is at position 3 (one position above the bottom position on the screen), tvs[3] is at position 4 (bottom of the screen). Thus, the value of positions[3] is 4 (the index of the TextView that is at position 3), and the value of positions[4] is 3 (the index of the TextView that is at position 4). The other TextViews have not been moved yet.

The tvPosition method, coded at lines 89–93, returns the position (0, 1, 2, 3, or 4 in this example) of the array element of tvs at index tvIndex, the parameter of the method. The expression params[tvIndex].topMargin returns the y-coordinate of the top of the TextView. We add half the height of a TextView and then divide by the height of a TextView in order to compute its position within this PuzzleView.

Inside the updateStartPositions method, coded at lines 67–71, we update the value of emptyPosition at line 70. The emptyPosition instance variable stores the position within the PuzzleView of the TextView that was touched. We retrieve it by calling the tvPosition method, passing the array index of that TextView (line 70).

FIGURE 7.6 A possible start of the puzzle

FIGURE 7.7 After switching I LOVE and JAVA

The placeTextViewAtPosition method is coded at lines 95–109. We call it when the user releases a piece of the puzzle. It swaps it with the piece underneath it. Its first parameter, tvIndex, is the index of the TextView that the user is releasing. Its second parameter, toPosition, is the position that TextView will be after the user releases it and ends the touch event. This method does three things:

  • ▸ Place the TextView (TextView A) at index tvIndex to its new position, identified by toPosition. It is replacing a TextView at that position (TextView B).

  • ▸ Place TextView B at the old position of TextView A, emptyPosition.

  • ▸ Update the positions array.

FIGURE 7.8 shows the various states of the TextViews and those variables as the user picks up the I LOVE piece of the puzzle and moves it to where the PROGRAMMING piece of the puzzle is. At that stage, the user is moving the TextView at index 4 (the value of tvIndex) and position 3 (the value of emptyPosition) to the position 0 (the value of toPosition), where the TextView at index 1 (the value of positions[toPosition]) is.

The tvs and params arrays parallel each other. At line 98, we set the top margin of the params array element at index tvIndex to the y-coordinate where that tvs array element is moving to. At line 99, we force a re-layout of the TextView array element at tvIndex based on the value of its corresponding params array element, effectively placing the TextView in its new slot. At lines 101–104, we do the same for the TextView element that was at position toPosition. It is moving to the position emptyPosition. At lines 106–108, we update the positions array to reflect that the two TextViews have been moved.

The positions array is instantiated at line 30 inside the buildGuiByCode method and initialized at line 52 inside the fillGui method.

FIGURE 7.8 Moving I LOVE to the PROGRAMMING position

FIGURE 7.9 The Puzzle app, Version 2, after the user solves the puzzle

The currentSolution method, coded at lines 111–118, constructs and returns a String array that reflects the current positions of the TextViews in our puzzle. The value of positions[i] is the index of the TextView that is at position i within the screen as illustrated in Figures 7.6 and 7.7. We access the text inside the TextView at position i within the PuzzleView using the expression tvs[positions[i]].getText( ). Because the getText method returns a CharSequence, we call toString to convert the returned value to a String.

FIGURE 7.9 shows the solved puzzle.

7.7 Gesture and Tap Detection and Handling

Sometimes we need to detect a confirmed single tap, or a double tap. A single tap is confirmed when it is identified by the system as a single tap only (i.e., no tap following the first tap has been detected). Or we need to detect the speed of a swipe to assign to the velocity of an object on the screen. The GestureDetector class, along with its static inner interfaces, GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener, provide the tools and functionality for gestures and taps.

  • ▸ The GestureDetector.OnGestureListener interface notifies us of gestures via its six callback methods (listed in TABLE 7.8).

  • ▸ The GestureDetector.OnDoubleTapListener interface notifies us of double taps or confirmed single taps via its three callback methods (listed in TABLE 7.9).

TABLE 7.10 shows some methods of the GestureDetector class, including a constructor, the setOnDoubleTapListener, and the onTouchEvent methods.

TABLE 7.8 Methods of the GestureDetector.OnGestureListener interface

Method Description
boolean onDown( MotionEvent e ) Called on the DOWN action when a touch occurs.
boolean onFling( MotionEvent e1, MotionEvent e2, float velocityX, float velocityY ) Called when a swipe occurs. Velocity is measured in pixels per second in both directions.
void onLongPress( MotionEvent me) Called when a long press occurs.
boolean onScroll( MotionEvent e1, MotionEvent e2, float distanceX, float distanceY ) Called when a scroll occurs. Distances are measured between this onScroll method call and the previous one.
void onShowPress( MotionEvent e ) Called when the user touches the screen and does not move or lift up his or her finger.
boolean onSingleTapUp( MotionEvent e ) Called on the UP action when a touch occurs.

TABLE 7.9 Methods of the GestureDetector.OnDoubleTapListener interface

Method Description
boolean onDoubleTap( MotionEvent e ) Called on the DOWN action when a double tap occurs.
boolean onDoubleTapEvent( MotionEvent e ) Called on the DOWN, possibly MOVE, and UP actions when a double tap occurs.
boolean onSingleTapConfirmed( MotionEvent e ) Called on the DOWN action when a confirmed single tap occurs (i.e., the single tap is not the first tap of a double tap).

TABLE 7.10 Selected methods of the GestureDetector class

Method Description
GestureDetector( Context context, GestureDetector.OnGestureListener gestureListener ) Creates a GestureDetector object for the context and using gestureListener as the listener called for gesture events. We must use this constructor from a User-Interface thread.
void setOnDoubleTapListener( GestureDetector.OnDoubleTapListener doubleTapListener ) Sets doubleTapListener as the listener called for double tap and related gestures.
boolean onTouchEvent( MotionEvent e ) Called when a touch event occurs; triggers a call to the appropriate callback methods of the GestureDetector.OnGestureListener interface.

In order to handle a touch event using the GestureDetector class and the Gesture-Detector.OnDoubleTapListener and GestureDetector.OnGestureListener interfaces, we need to:

  • ▸ Declare a GestureDetector instance variable.

  • ▸ Define a handler class that implements the GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener interfaces and overrides their methods.

  • ▸ Create a handler object of that class.

  • ▸ Instantiate the GestureDetector instance variable and pass the handler object as the second argument of the constructor.

  • ▸ Set the handler object as the listener handling the tap events (if necessary).

  • ▸ Inside the onTouchEvent method of the Activity class, call the onTouchEvent method of the GestureDetector class with the GestureDetector instance variable. This triggers the dispatching of the touch event: depending on the touch event, the appropriate method of the handler class will be automatically called.

We can define the handler class as a private class of the current activity class. The following code sequences would implement the preceding:

//   GestureDetector instance variable inside the activity class
private GestureDetector detector;

//   Inside the onCreate method of the activity class
GestureAndTapHandler gth = new GestureAndTapHandler( );
detector = new GestureDetector( this, gth );
detector.setOnDoubleTapListener( gth );
//   Inside the onTouchEvent method of the activity class
//   event is the MotionEvent parameter of the onTouchEvent method
detector.onTouchEvent( event );
//   Private class inside the activity class
private class GestureAndTapHandler implements
              GestureDetector.OnGestureListener,
              GestureDetector.OnDoubleTapListener {
 //   The GestureAndTapHandler class needs to override all 9 methods
}

As before with the View.OnTouchListener interface, an alternative to defining a new class implementing GestureDetector.OnGestureListener and GestureDetector. OnDoubleTapListener is for the current activity class to implement them. Then, we would need to override the nine methods inside the Activity class and we would use this instead of gth in the preceding code.

The onTouchEvent method of the GestureDetector class acts as a dispatcher and calls the appropriate method or methods of the GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener interfaces based on the nature of the event that just happened. For some apps, it is possible that all we need is to execute inside the onTouchEvent method. For other apps, we may be interested in capturing double taps and, therefore, we will want to place our code inside the onDoubleTapEvent method. Or we may be interested in capturing the speed of a swipe and therefore we will want to place our code inside the onFling method. Depending on what action we are interested in capturing and processing, we place our code in the corresponding method. Note that when we override onTouchEvent, we need to call the onTouchEvent method of the GestureDetector class because it performs the dispatching.

EXAMPLE 7.10 shows the MainActivity class of a practice app. It implements the GestureDetector.OnGestureListener and the GestureDetector.OnDoubleTapListener interfaces at lines 10–11. We declare a GestureDetector instance variable, detector, at line 13. We instantiate it at line 17, passing this as the first and second argument. As the first argument, this represents the application’s context (an Activity object “is a” Context object). As the second argument, it represents a GestureDetector.OnGestureListener (MainActivity inherits from GestureDetector.OnGestureListener and, therefore, this “is a” GestureDetector.OnGestureListener). Thus, since detector calls its onTouchEvent method at line 23, gestures events will trigger calls to some of the six callback methods implemented in the MainActivity class depending on the touch event. At line 18, we set this as the listener registered by detector as the listener for tap events. Thus, since detector calls its onTouchEvent method at line 23, tap events will trigger calls to some of the three callback methods inherited from GestureDetector.OnDoubleTapListener and implemented in the MainActivity class, depending on the touch event.

EXAMPLE 7.10 The MainActivity class, Touches app, Version 2

The onTouchEvent method is coded at lines 21–25. Detector calls its own onTouchEvent method at line 23, passing the MotionEvent event, setting up the dispatching discussed previously. All the nine overridden methods of the two listeners interface output a feedback statement to Logcat.

FIGURE 7.10 Possible Logcat output when the user taps on the screen once, Touches app, Version 2

FIGURE 7.10 shows the output inside Logcat of Example 7.10 when the user taps on the screen once on the emulator. It shows that the onTouchEvent method triggers calls to the onDown, onSingleTapUp, and onSingleTapConfirmed, successively. When onSingleTapUp is called, we do not know yet that this was a single tap event, because a double tap event would also trigger a call to onSingleTapUp. Thus, if we want to execute some code based on a single tap event only, we should place that code inside the onSingleTapConfirmed method.

FIGURE 7.11 Possible Logcat output when the user double taps on the screen, Touches app, Version 2

FIGURE 7.12 Possible Logcat output when the user swipes the screen, Touches app, Version 2

FIGURE 7.11 shows the output inside Logcat of Example 7.10 when the user taps on the screen twice quickly (i.e., double taps on the emulator). It shows that the onTouchEvent method triggers calls to the onDown, onSingleTapUp, onDoubleTap, onDoubleTapEvent, and the onDown and onDoubleTapEvent again. It is possible that we get a slightly different output, possibly an additional call to onTouchEvent and onDoubleTapEvent. Note that the onSingleTapConfirmed method is not called. If we want to process taps, we can place our code inside the onSingleTapUp and onDoubleTap methods. If we want to process the double tap event, we can place our code inside the onDoubleTapEvent method. Note that it is called twice, once on a DOWN action and the second time on an UP action. It can even be called three times, if a (slight) MOVE action is detected in between the DOWN and UP actions.

A triple tap is interpreted as a double tap followed by a single tap. A quadruple tap is interpreted as a double tap followed by another double tap.

FIGURE 7.12 shows a possible output inside Logcat of Example 7.10 when the user swipes the screen. The onScroll method is called several times, at various points within the swipe. The onFling method is called once and last. It is possible that we get a slightly different output depending on the swipe or if we use the emulator and the computer mouse.

Once we have decided what method we want to execute in, we can use its parameter or parameters to retrieve values relative to the action that is taking place. For example, if we want to capture the point of origin and the velocity of a swipe, we can place our code inside the onFling method and retrieve these values as shown in EXAMPLE 7.11.

As we run the modified app and swipe the screen, the output looks like the one in FIGURE 7.13.

EXAMPLE 7.11 Expanded onFling method within the MainActivity class, Touches app, Version 3

FIGURE 7.13 Logcat output when the user swipes the screen, Touches app, Version 3

7.8 Detecting a Double Tap, Puzzle App, Version 3

The GestureDetector.SimpleOnGestureListener is a convenient class that implements the GestureDetector.OnGestureListener and the GestureDetector.OnDoubleTapListener interfaces with do-nothing methods returning false. So if we are interested in only one or two methods of the nine methods in these two interfaces, it is easier to extend that class than to implement one or both interfaces. In our Puzzle app, Version 3, we want to process a double tap and change MOBILE to ANDROID when the user double taps on the TextView that shows MOBILE. We can ignore the other eight methods and only need to code inside the onDoubleTapEvent method.

We update the Puzzle class, our Model, and add two methods to it: one that returns the word to be changed, and one that returns the replacement word. We keep this change as simple as possible in order to focus more on the Controller and View parts of the app. EXAMPLE 7.12 shows these two methods.

EXAMPLE 7.12 The wordToChange and replacementWord methods of the Puzzle class, Puzzle app, Version 3

In order to use the GestureDetector.SimpleOnGestureListener class, we can do the following:

  • ▸ Code a private class that extends GestureDetector.SimpleOnGestureListener and override inside that class the method(s) that we are interested in.

  • ▸ Declare and instantiate an object of that class, a gesture handler.

  • ▸ Declare a GestureDetector instance variable. When instantiating it, pass the gesture handler as the second argument of the constructor.

  • ▸ If we are interested in processing tap events, call setDoubleTapListener with the gesture detector instance variable and pass the gesture handler.

  • ▸ Override the onTouchEvent method of the Activity class inside the subclass of Activity. Inside it, call the onTouchEvent method of the GestureDetector class with the gesture detector instance variable. That sets up the dispatching of touch events to the appropriate method(s) of the gesture handler class.

Note that MainActivity already extends AppCompatActivity and, therefore, cannot extend GestureDetector.SimpleOnGestureListener. Thus, contrary to Example 7.10, in which MainActivity implements both listener interfaces, we need to code a separate class that extends GestureDetector.SimpleOnGestureListener.

EXAMPLE 7.13 shows the new parts of the MainActivity class. The private class DoubleTapHandler, coded at lines 93–106, only overrides the onDoubleTapEvent method because we are only interested in processing a double tap event. In it, we retrieve the index of the TextView that the user double tapped on, check if its label is MOBILE, and change it to ANDROID if it is. We first retrieve the y-coordinate of the double tap within the PuzzleView at line 96. We then call the indexOfTextView method (which we add to the PuzzleView class) of PuzzleView to obtain the index of the TextView where the double tap happened (lines 99–100). Because we need to subtract the heights of the status bar and action bar from the y-coordinate of the double tap, we declare statusBarHeight and actionBarHeight as instance variables at lines 19–20. In this way, we do not have to compute their value twice.

EXAMPLE 7.13 Additions to the MainActivity class, Puzzle app, Version 3

At lines 101–102, we retrieve the text inside that TextView by calling the getTextViewText method (which we also add to the PuzzleView class) and test if it matches the word to change in the Puzzle class. If it does, we call the setTextViewText method (which we also add to the PuzzleView class) to change the text of the TextView to the new word retrieved from the Puzzle class. From the Model-View-Controller perspective, the Controller asks the View to retrieve the text of the TextView and compares it to the word to change retrieved from the Model. If they match, the Controller asks the Model for the replacement word and tells the view to place it inside the TextView at line 103.

At line 57, we declare and instantiate dth, a DoubleTapHandler object. The instance variable detector, a GestureDetector, is declared at line 21 and instantiated at line 58. The second argument passed to the constructor is dth. That means that the gesture-related methods of the GestureDetector.OnGestureListener interface, which DoubleTapHandler class inherits via the GestureDetector.SimpleOnGestureListener class, will be called when a gesture event happens. However, we are not interested in handling simple touches, swipes, or flings in this case. We are only interested in handling double taps. Thus, we call the setOnDoubleTapListener method at line 59 so that the three methods inherited from the GestureDetector.OnDoubleTapListener interface are called when a tap event occurs. The GestureDetector class, which belongs to the android.view package, is imported at line 9.

We override the onTouchEvent method at lines 88–91. At line 89, detector calls the onTouchEvent of the GestureDetector class, which sets up the dispatching of gestures and tap events to the methods of the class that dth belongs to, DoubleTapHandler. Thus, when the user double taps on the screen, we execute inside the onDoubleTapEvent of our DoubleTapHandler class.

EXAMPLE 7.14 shows the three methods added to the PuzzleView class. Inside the indexOfTextView method (lines 120–124), we divide the y-coordinate of the tap by the height of a TextView and assign it to position at line 122. The index of the TextView located at position is positions[position]. We return it at line 123.

Inside getTextViewText (lines 126–129), we call toString at line 128 because the getText method of TextView returns a CharSequence.

EXAMPLE 7.14 The three methods added to the PuzzleView class, Puzzle app, Version 3

If we run the app and double tap on MOBILE after we solve the puzzle, it changes to ANDROID. Note that if we double tap on either I LOVE, PROGRAMMING, USING, or JAVA, nothing happens. The onTouch method, which is part of the MainActivity class and is shown at lines 54–78 of Example 7.8, returns true. That means that we stop propagating the touch event. As we run the app, if we double tap on MOBILE before the puzzle is solved, nothing happens because the onDoubleTapEvent method does not execute. Once the puzzle is solved, we disable listening on the TextViews (at line 74 of Example 7.8). There is no longer a touch listener registered on the TextViews, so the touch event’s target is now the content View for the activity, and the event is dispatched by the onTouchEvent method to the onDoubleTapEvent method.

If we change the onTouch method so that it returns false at line 77 of Example 7.8, then a double tap event on a TextView would propagate to the content View, which is the View underneath the View that is the target of the touch. In that case, the onDoubleTapEvent method would execute. If we double tap on MOBILE, it would change to ANDROID. More generally, if the onTouch method returns false, not only the touch event happens on the top View but also propagates to Views underneath that top View.

FIGURE 7.14 shows the state of the puzzle after the user solved it and double tapped on MOBILE.

FIGURE 7.14 The Puzzle app, Version 3, after the user solves the puzzle and double taps on MOBILE

7.9 Making the App Device Independent, Puzzle App, Version 4

When we publish an app, we do not know what device it will run on, in particular what the screen width and height dimensions will be. In Version 4, we use our DynamicFontSizing class in EXAMPLE 7.15 so that the size of the font is optimal for the device on which the app runs. Given a TextView, the setFontSizeToFitInView static method (lines 11–32) sizes the text font inside the TextView so that it is maximal and the text fits on one line—it returns that maximal font size. Appendix B explains in detail the DynamicSizing class.

EXAMPLE 7.15 The DynamicFontSizing class, Puzzle app, Version 4

EXAMPLE 7.16 The updated fillGui method of the PuzzleView class, Puzzle app, Version 4

EXAMPLE 7.16 shows the updated fillGui method, the only change in the PuzzleView class, in addition to importing the TypedValue class from the android.util package. We optimize the font size for the TextViews in the fillGui method because that is where their text is set.

We want to use the same font size for all the TextViews. However, the text inside each TextView is different. Thus, we compute the minimum font size returned by all the calls to setFontSizeToFitInView for each TextView. After initializing minFontSize at line 51 with MAX_FONT_SIZE, we update it as necessary at lines 59–62. Once minFontSize is set, we loop again through all the TextViews and reset their font size with minFontSize at lines 65–67. After this method executes, all the TextViews have the same font size, and it is the maximal font size so that their text contents fit on one line.

Before we compute the font size for each TextView, we make sure that their width is equal to the width of the screen at line 56. We give some padding to each TextView at line 57 so that there is space around the text. The font size ends up being 46 when running on the Nexus 5 emulator.

FIGURE 7.15 shows the Puzzle app, Version 4, running inside the emulator. PROGRAMMING fills up almost the whole TextView.

FIGURE 7.15 The Puzzle app, Version 4, running inside the emulator

Chapter Summary

  • We can use the View.OnTouchListener interface to capture and handle a touch event.

  • To register a View.OnTouchListener on a GUI component, we can use the setOnTouchListener method of the View class.

  • The onTouch callback method of the View.OnTouchListener interface is called when a touch event occurs.

  • The onTouch method includes a View parameter that is a reference to the View on which the touch happened.

  • The onTouch method includes a MotionEvent parameter that contains information about the current touch event, such as the x- and y-coordinates, the type of event, etc.

  • If the onTouch method returns true, the touch event does not propagate to Views below the current View that might have an event handler for that touch event. If it returns false, it does.

  • The getAction method of the MotionEvent class returns an integer value representing the type of action that just occurred within the touch event.

  • The MotionEvent class includes some constants to identify a touch event action, such as ACTION_DOWN, ACTION_MOVE, or ACTION_UP.

  • View components are displayed inside a ViewGroup using a stacking order. The View added first is at the bottom of the stack, whereas the View added last is at the top of the stack.

  • We can use the bringToFront method to bring a View to the top of the stack in order to guarantee that that View is not hidden by other Views.

  • We can use the GestureDetector class, along with its static inner interfaces, GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener, to capture and handle gesture and tap events.

  • Inside the onTouchEvent method of the activity class, a GestureDetector object is expected to call its onTouchEvent method to set up the dispatching of the touch event to the appropriate method of the handler class that implements the GestureDetector.OnGestureListener and/or GestureDetector.OnDoubleTapListener interfaces.

  • The GestureDetector.SimpleOnGestureListener is a convenient class that implements the GestureDetector.OnGestureListener and the GestureDetector.OnDoubleTapListener interfaces with do-nothing methods.

Exercises, Problems, and Projects

Multiple-Choice Exercises

  1. What method do we use to register a View.OnTouchListener on a component?

    • setOnTouchListener

    • addOnTouchListener

    • registerOnTouchListener

    • isOnTouchListener

  2. What method of the MotionEvent class do we use to retrieve the type of action that just happened?

    • action

    • getAction

    • getEvent

    • getTouch

  3. What method do we use to bring a View to the top of the stacking order?

    • bringToTop

    • gotToTop

    • bringToFront

    • bringChildToTop

  4. What class can be used to capture gestures and tap events?

    • Gesture

    • GestureDetector

    • TapDetector

    • GestureAndTapDetector

  5. OnGestureListener and OnDoubleTapListener are

    • Private static inner classes of GestureDetector

    • Private static inner interfaces of GestureDetector

    • Public static inner interfaces of GestureDetector

    • Classes independent of GestureDetector

  6. In order to identify a touch event action, the MotionEvent class has

    • A special constructor

    • Private instance variables

    • Private methods

    • Constants that the action can be compared to

  7. What method of the GestureDetector class acts as a dispatcher to the various methods of OnGestureListener and OnDoubleTapListener?

    • onTouch

    • onTouchEvent

    • onMotionTouchEvent

    • onEvent

  8. How many methods do OnGestureListener and OnDoubleTapListener have, respectively?

    • six and six

    • three and three

    • three and six

    • six and three

  9. When inheriting from OnGestureListener and OnDoubleTapListener, if we are only interested in one or two methods, an alternative is to

    • Implement the SimpleOnGestureListener interface

    • Extend the SimpleOnGestureListener class

    • Implement the SimpleOnGestureListener class

    • Extend the SimpleOnGestureListener interface

  10. Which method is not a method of OnDoubleTapListener?

    • onSingleTapConfirmed

    • onDoubleTapConfirmed

    • onDoubleTap

    • onDoubleTapEvent

Fill in the Code

  1. Write the class header of a class named MyActivity that inherits from Activity and View.OnTouchListener.

  2. We are coding inside the onTouch method of View.OnTouchListener. If it is a DOWN action, assign the x-coordinate of the touch to the variable x1. If it is an UP action, assign the y-coordinate of the touch to the variable y2.

  3. Write the code to place a View named view at the top of the stack it belongs to.

  4. We have already coded a class named MyHandler that extends View.OnTouchListener. Write the code to use an object of that class so that we can listen to touch events occurring inside a View named myView.

  5. Write the class header of a class named MyActivity that inherits from Activity, GestureDetector.OnGestureListener, and GestureDetector.OnDoubleTapListener.

  6. Write a private class that inherits from GestureDetector.SimpleOnGestureListener. We only want to process single tap events. Every time there is a single tap, we color the background of a View named myView with a random color.

  7. Write a private class that inherits from GestureDetector.SimpleOnGestureListener. We want to count the number of taps and accumulate them in an instance variable of an Activity class named total, which has already been initialized. Every time there is a tap, total goes up by 1.

    The following applies to Questions 18 and 19:

    • The MyActivity class extends Activity and implements GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener. We have declared an instance variable of type GestureDetector named d as follows:

      private GestureDetector d;
      
  8. We are coding inside the onCreate method of an Activity class. Write the code so that the current Activity will handle the gestures and tap events.

    protected void onCreate( Bundle savedInstanceState ){
       super.onCreate( savedInstanceState );
       // Your code goes here
    }
    
  9. We are coding inside the onTouchEvent method of an Activity class. Write the code so that if there is a gesture event, it gets dispatched to the appropriate method of GestureDetector.OnGestureListener.

    public boolean onTouchEvent( MotionEvent event ){
      // Your code goes here
    }
    

Write an app

  1. Modify Example 7.13 so that MainActivity does not implement View.OnTouchListener. Instead, the touch event handler is a private class.

  2. Modify Example 7.10 so that MainActivity does not implement GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener. Instead, they are private classes.

  3. Modify Example 7.13 so that when the user double taps, we restart a new puzzle.

  4. Write an app that shows in one or more TextViews the distances of a swipe along each axis whenever the user swipes the screen.

  5. Write an app that has two TextViews. One of them has a red background—when the user taps inside it, it changes to blue, and when the user double taps inside it, it changes back to red, and so on. If the user taps or double taps outside the TextView, its color does not change. The other TextView displays the accumulated tap count.

  6. Write an app that has one TextView that has a red background—when the user taps inside it, it becomes invisible, and when the user double taps anywhere on the screen, it becomes visible again.

  7. Write an app that displays a chessboard using two colors—as the user taps, the colors change to the next pair of colors, and so on. We should include at least five pairs of colors that the app cycles through. Include a Model.

  8. Write an app that has three activities—each activity has one TextView that displays some text that identifies the activity. For each activity, if the user swipes the screen from right to left, the app moves to the next activity (from activity 1 to activity 2, or from activity 2 to activity 3). If the user swipes the screen from left to right, the app moves back to the previous activity (from activity 3 to activity 2, or from activity 2 to activity 1).

  9. Write an app that shows a TextView in the middle of the screen. The user can touch it and fling it toward an edge of the screen (the TextView should follow the fling). If the x or y velocity is above a certain threshold (that you determine), the TextView disappears at the end of the fling and reappears at some random location on the screen, and so on.

  10. Write an app that implements the tile puzzle game: on a 3 × 3 grid, the digits 1 through 8 are randomly positioned inside TextViews on the grid. By touching a TextView, it moves to its adjacent empty square (if it is next to the empty square). When the digits are in order (1, 2, 3 on the first row, 4, 5, 6 on the second row, and 7, 8 on the last row), the game is over and touch events are disabled. Include a Model.

  11. Write an app that implements the following algebra game: five TextViews display two random integers between 1 and 9, the + operator, the = operator, and the result of the addition. When the user double taps the + operator, the result shows. When the user double taps outside the + operator, two new random digits take the place of the existing ones. Include a Model.

  12. Write an app that implements the following total accumulator using two TextViews. One contains a randomly generated digit between 1 and 9, the other displays the current total, whose initial value is 0. When the user touches the first TextView and brings it over the other TextView and releases it, the digit is added to the total, and the first TextView goes back to its original position and displays a new random digit between 1 and 9. Include a Model.

  13. Write an app that simulates the dealing of the community cards in a game of poker (Texas hold’em). The first three cards are dealt on a double tap. The last two cards should be dealt on a single tap, one at a time. A swipe hides the cards so that we are ready to start another round. Cards are represented by TextViews. Include a Model.

  14. Write an app that simulates a modified game of blackjack. The first two cards are dealt on a double tap. Additional cards are dealt one by one on a single tap only (not a double tap). Cards are represented by TextViews. Another TextView shows the result. If the total is more than 17, taps should be disabled and the user wins. If the total goes beyond 21, the user loses. Include a Model.

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

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