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.
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
.
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 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.
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.
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.
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).
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).
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:
▸ 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.
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.
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.
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.
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
.
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.
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.
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.
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.
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).
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.
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.
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.
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 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 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.
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.
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.
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
.
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.
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.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
.
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.
What method do we use to register a View.OnTouchListener on a component?
setOnTouchListener
addOnTouchListener
registerOnTouchListener
isOnTouchListener
What method of the MotionEvent class do we use to retrieve the type of action that just happened?
action
getAction
getEvent
getTouch
What method do we use to bring a View to the top of the stacking order?
bringToTop
gotToTop
bringToFront
bringChildToTop
What class can be used to capture gestures and tap events?
Gesture
GestureDetector
TapDetector
GestureAndTapDetector
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
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
What method of the GestureDetector class acts as a dispatcher to the various methods of OnGestureListener and OnDoubleTapListener?
onTouch
onTouchEvent
onMotionTouchEvent
onEvent
How many methods do OnGestureListener and OnDoubleTapListener have, respectively?
six and six
three and three
three and six
six and three
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
Which method is not a method of OnDoubleTapListener?
onSingleTapConfirmed
onDoubleTapConfirmed
onDoubleTap
onDoubleTapEvent
Write the class header of a class named MyActivity that inherits from Activity and View.OnTouchListener.
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
.
Write the code to place a View named view at the top of the stack it belongs to.
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.
Write the class header of a class named MyActivity that inherits from Activity, GestureDetector.OnGestureListener, and GestureDetector.OnDoubleTapListener.
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.
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;
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 }
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 }
Modify Example 7.13 so that MainActivity does not implement View.OnTouchListener. Instead, the touch event handler is a private class.
Modify Example 7.10 so that MainActivity does not implement GestureDetector.OnGestureListener and GestureDetector.OnDoubleTapListener. Instead, they are private classes.
Modify Example 7.13 so that when the user double taps, we restart a new puzzle.
Write an app that shows in one or more TextViews the distances of a swipe along each axis whenever the user swipes the screen.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
3.16.212.27