Sometimes the number of buttons (or other components) we want to display is dynamic—it could be different every time we run the app, such as in a content app. A content app is an app that gets its data from an external source, for example a website such as Facebook or Twitter, displays the data, and enables the user to interact with it. We do not know in advance how much data will be retrieved. It is also possible that an app lends itself well to a particular data structure, like a 3 × 3 two-dimensional array in a tic-tac-toe game. We can implement the GUI part of a tic-tac-toe game with nine buttons and we could define the nine buttons in the activity_main.xml file, but it is easier to manipulate a 3 × 3 two-dimensional array of buttons by code.
In this chapter, we build a Tic-Tac-Toe app, and create the GUI and handle the events programmatically. We leave the automatically generated activity_main.xml file as it is, and do everything by code.
Although we define the View
by code rather than in the activity_main.xml file, we still use the Model-View-Controller architecture. In order to define the View, we need to understand what will be in it. That depends on how the TicTacToe game is defined and played; that part is encapsulated in the Model. Thus, we define the Model first.
The Model is comprised of the TicTacToe
class, which encapsulates the functionality of the TicTacToe game. Again, the Model is independent from any visual representation. In this app, it enables play and enforces the rules of the game. The API for this class is shown in TABLE 3.1.
EXAMPLE 3.1 shows the class code. The TicTacToe
constructor (lines 8–11) calls reset-Game
at line 10 after instantiating the two-dimensional array game
as a 3 × 3 array. Having a separate resetGame
method enables us to reuse the same object when playing back-to-back games. ResetGame
(lines 80–85) clears the board by setting all the cells in game
to 0; it also sets turn
to 1 so that player 1 starts.
The method play
(lines 13–26) first checks if the play is legal (lines 15–16). If it is not, it returns 0 (line 25). If it is, it updates game
at line 17 and turn
at lines 18–21. It then returns the value of currentTurn
(line 22), which holds the old value of turn
(line 14).
The method whoWon
(lines 28–39) checks if a player has won the game and returns the player number if that is true; otherwise, it returns 0. We break down that logic using three protected
methods: checkRows
(lines 41–47), checkColumns
(lines 49–55), and checkDiagonals
(lines 57–65). There is no reason for a client of that class to access these methods so we declare them protected
.
TABLE 3.1 API for the TicTacToe
class
Instance Variables | |
---|---|
private int[ ][ ] game | 3 × 3 two-dimensional array representing the board |
private int turn | 1 = player 1’s turn to play; 2 = player 2’s turn to play |
Constructor | |
TicTacToe( ) | Constructs a TicTacToe object; clears the board and prepares to play by setting turn to 1 |
Public Methods | |
int play( int row, int column ) | If the play is legal and the board cell is not taken, plays and returns the old value of turn |
boolean canStillPlay( ) | Returns true if there is still at least one cell not taken on the board; otherwise, returns false |
int whoWon( ) | Returns i if player i won, 0 if nobody has won yet |
boolean gameOver( ) | Returns true if the game is over, false otherwise |
void resetGame( ) | Clears the board and sets turn to 1 |
The method isGameOver
(lines 76–78) returns true
if the game is over (i.e., somebody has won or no one can play); otherwise, it returns false
.
In Version 0 of our TicTacToe app, we use the empty activity template and only setup the GUI. We use a 3 × 3 two-dimensional array of Buttons
, in order to mirror the 3 × 3 two-dimensional array game
in our Model, the TicTacToe
class. In order to keep things simple, we first place the View inside the Activity
class, so the View and the Controller are in the same class. Later in the chapter, we separate the View from the Controller and place them in two different classes.
The Android framework provides classes, called layout classes, to help us organize window contents. We have already used the RelativeLayout
class, which is the default layout that is automatically used in activity_main.xml when we create a project. These layout classes are subclasses of the abstract
class ViewGroup
, itself a subclass of View
. A ViewGroup
is a special View
that can contain other Views
, called its children.
TABLE 3.2 shows a few layout classes along with their descriptions. FIGURE 3.1 shows the hierarchy of these classes.
In order to display the nine buttons in a 3 × 3 grid, we use a GridLayout
.
As we know, there are many types of Android phones and tablets, and they all can have different screen dimensions. Thus, it is bad practice to hardcode widget dimensions and coordinates because a user interface could look good on some Android devices but poorly on others. In order to keep this example simple, we will assume that the user will only play in vertical orientation; thus, we assume that the width of the device is smaller than its height.
TABLE 3.2 ViewGroup
and selected subclasses
Class | Description |
---|---|
ViewGroup | A View that contains other Views. |
LinearLayout | A layout that arranges its children in a single direction (horizontally or vertically). |
GridLayout | A layout that places its children in a rectangular grid. |
FrameLayout | A layout designed to block out an area of the screen to display a single item. |
RelativeLayout | A layout where the positions of the GUI components can be described in relation to each other or to their parent. |
TableLayout | A layout that arranges its children in rows and columns. |
TableRow | A layout that arranges its children horizontally. A TableRow should always be used as a child of a TableLayout. |
We can dynamically retrieve, by code, the width and height of the screen of the device that the app is currently running on. Using that information, we can then size the GUI components so that they fit within the device’s screen no matter what the brand and model of the device are. In setting up the View, we perform the following steps:
▸ Retrieve the width of the screen
▸ Define and instantiate a GridLayout
with three rows and three columns
▸ Instantiate the 3 × 3 array of Buttons
▸ Add the nine Buttons
to the layout
▸ Set the GridLayout
as the layout manager of the view managed by this activity
In EXAMPLE 3.2, the method buildGuiByCode
(lines 19–42) implements the above.
TABLE 3.3 Resources involved in retrieving the width of the screen
Class or Interface | Package | Method and fields |
---|---|---|
Activity | android.app | WindowManager getWindowManager( ) |
WindowManager | android.view | Display getDefaultDisplay( ) |
Display | android.view | void getSize( Point ) |
Point | android.graphics |
x and y public instance variables |
At line 10, we declare the instance variable buttons
, a two-dimensional array of Buttons
. The Button class is imported at line 6.
At lines 20–23, we retrieve the width of the screen. At line 21, we declare a Point
variable, size
. The Point
class, imported at line 3, has two public
instance variables, x
and y
.
At line 22, we chain three method calls, successively calling getWindowManager
, getDefaultDisplay,
and getSize
. GetWindowManager
, from the Activity
class, returns a WindowManager
object that encapsulates the current window. With it, we call the method getDefaultDisplay
of the WindowManager
interface; it returns a Display
object, which encapsulates the current display and can provide information about its size and its density. With it, we call the getSize
method of the Display
class; getSize
is a void
method but takes a Point
object reference as a parameter. When that method executes, it modifies the Point
parameter object and assigns to it the width and height of the display as its x
and y
instance variables. At line 23, we retrieve the width of the screen (size.x
) and assign one third of it to the variable w
. We later use the value of w
to dimension the Buttons
so that we can place three of them across the screen. TABLE 3.3 summarizes the resources involved in retrieving the size of the current device’s screen.
At lines 25–28, we define and instantiate the GridLayout
object gridLayout
.
The GridLayout
class belongs to the android.widget
package; we import it at line 7. At line 26, we instantiate a GridLayout
object, passing the argument this
to the GridLayout
constructor; as shown in TABLE 3.4, the GridLayout
constructor takes a Context
parameter. Context
is an interface that encapsulates global information about the app environment. As shown in FIGURE 3.2, the Activity
class inherits indirectly from the Context
class, therefore, an Activity
object “is a” Context
object and this
can be used as the Context
object representing the app environment. At lines 27–28, we set the GridLayout’
s number of columns and rows to 3.
The Buttons
are created and added to the layout at lines 30–37 by looping through the array buttons
. We first instantiate buttons
at line 31 as a 3 × 3 two-dimensional array of Buttons
. The double loop at lines 32–37 instantiates each Button
and adds it to gridLayout
. The Button
constructor called at line 34 takes a Context
parameter representing the app environment. Again, we pass this
, representing the current Context
, as the argument. It is important to instantiate a GUI component before either calling a method with it or adding it to a View
. Otherwise, we will have a NullPointerException
at runtime and the app will stop. At line 35, we add the current button to the layout using the addView
method and specify its width and height as equal to w
. The GridLayout
class inherits addView
from ViewGroup
.
TABLE 3.4 GridLayout
constructor and methods
Constructor | |
---|---|
GridLayout( Context context ) | Constructs a GridLayout within the app environment defined by context. |
Public Methods | |
setRowCount( int rows ) | Sets the number of rows in the grid to rows. |
setColumnCount( int cols ) | Sets the number of columns in the grid to cols. |
addView( View child, int w, int h ) | Method inherited from ViewGroup; adds child to this ViewGroup using width w and height h . |
Finally, at line 40, we set the contents of the view managed by this activity to gridLayout
by calling the setContentView
method from the Activity
class. Remember that GridLayout
inherits from ViewGroup
, which inherits from View
; therefore, gridLayout
“is a” View
.
The buildGuiByCode
method is called at line 16, inside the onCreate
method, which is called automatically when the app starts.
In order to keep things simple, we only allow the app to work in vertical orientation. Inside the AndroidManifest.xml file, we assign the value portrait
to the android:screenOrientation
attribute of the activity
tag.
FIGURE 3.3 shows the app running inside the emulator.
At this point, we have coded two important components of our app: the Model (the TicTacToe
class), and the View (the buildGuiByCode
method of the MainActivity
class). Next, we build the Controller.
In Version 1, we add code to capture a click on any button, identify what button was clicked, and we place an X inside the button that was clicked. At that point, we are not concerned about playing the game or enforcing its rules. We will do that in Version 2.
In order to capture and process an event, we need to:
Write an event handler (a class extending a listener interface)
Instantiate an object of that class
Register that object listener on one or more GUI components
EXAMPLE 3.3 shows the updated MainActivity
class, implementing the above.
The type of event that we want to capture determines the listener interface that we implement. View.OnClickListener
is the listener interface that we should implement in order to capture and handle a click event on a View
. Since it is defined inside the View
class, importing the View
class (line 7) automatically imports View.OnClickListener
. TABLE 3.5 lists the only abstract
method of that interface, onClick
. Our event handler class, ButtonHandler
, implements OnClickListener
and overrides onClick
. ButtonHandler
, implemented as a private
class, is coded at lines 53–61. At line 55, we add an output statement that gives feedback on the View
parameter of the onClick
method; we expect that it is a Button
. At lines 56–59, we loop through the array buttons
in order to identify the row and column values of the button that was clicked. We then call the update
method, passing these row and column values at line 59.
TABLE 3.5 The View.OnClickListener
interface
public abstract void onClick( View v ) | Called when a View has been clicked; the parameter v is a reference to that View. |
The update
method is coded at lines 48–51, but does not do much in this version. It outputs some feedback on the row and column of the button that was clicked (for debugging purposes), and writes an X inside it; this will change in Version 2.
We declare and instantiate a ButtonHandler
object at line 34 and we register it on each element of buttons
(line 39) as we loop through the array. Additionally, we set the text size of each button at line 38 so that it is relative to the size of each button, which we have sized relatively to the screen. In this way, we try to make our app as much device independent as possible. It is important to test an app on as many devices as possible and of various sizes to validate sizes and font sizes.
FIGURE 3.4 shows the app after the user clicked successively on three buttons. FIGURE 3.5 shows a partial output of Logcat, resulting from the output statements at lines 55 and 49. It reveals that the user clicked on the button in the top right corner first, then on the left button in the middle row, and then on the middle button in the bottom row. Figure 3.5 also shows the three different memory addresses of the three View
(Buttons
in this case) arguments of onClick
.
We now have a very simple Controller. It is made up of the onCreate
and update
methods, as well as the ButtonHandler
private class of the MainActivity
class. Note that in this app, the View and the Controller are in the same class. Later in the chapter, we put them in separate classes to make the code more reusable. The next step is to finish coding the Controller in order to enable game play.
We are assuming that two users will be playing on the same device against each other. Enabling game play does not just mean placing an X or an O on the grid of buttons at each turn. It also means enforcing the rules, such as not allowing someone to play twice at the same position on the grid, checking if one player won, indicating if the game is over. Our Model, the TicTacToe
class, provides that functionality. In order to enable play, we add a TicTacToe
object as an instance variable of our Activity
class, and we call the methods of the TicTacToe
class as and when needed. Play is happening inside the update
method so we have to modify it. We also need to check if the game is over and, in that case, disable all the buttons.
EXAMPLE 3.4 shows the updated MainActivity
class. At line 11, we declare a TicTacToe
object, tttGame
; we instantiate it at line 17.
Inside the update
method (lines 48–56), we first call the play
method of the TicTacToe
class with tttGame
at line 49. We know that it returns the player number (1 or 2) if the play is legal, 0 if the play is not legal. If the play is legal and the player is player 1, we write X inside the button (line 51). If the play is legal and the player is player 2, we write O inside the button (line 53). If the play is not legal, we do not do anything.
When the game is over, we want to disable all the buttons. At lines 58–62, the enableButtons
method enables all the buttons if its parameter, enabled
, is true
. If it is false
, it disables all the buttons. We can enable or disable a Button
by calling the setEnabled
method of the Button
class using the argument true
to enable the Button
, false
to disable it. We test if the game is over at line 54 and disable all the buttons if it is (line 55).
In Version 3, we will add a status label giving some feedback on the current state of the game.
FIGURE 3.6 shows the app running in the emulator. Player 1 just won and the buttons are disabled.
A lot of the Android GUI classes are inner classes or inner interfaces. For example, OnClick-Listener
is an inner interface of the View
class. The layout manager classes have inner classes that are used to specify the position of a View
child inside its parent layout. TABLE 3.6 shows some of them. The dot notation used in the class name indicates that a class is an inner class of another class. For example, GridLayout.LayoutParams
means that LayoutParams
is an inner class of GridLayout
.
The LayoutParams
classes are all public
, static
inner classes.
EXAMPLE 3.5 shows a very simple example of how a public
, static
, inner class, B
, can be defined inside another class, A
. Class B
is defined at lines 2–12. At line 2, the class header defines B
as public
and static
. It has a constructor (lines 5–7) and a toString
method (lines 9–11). For convenience, we refer to the class A.B
as opposed to class B
, inner class of A
.
TABLE 3.6 Selected LayoutParams
classes
Class | Description |
---|---|
ViewGroup.LayoutParams | Base class for the LayoutParams classes |
GridLayout.LayoutParams | Layout info associated with a child of a GridLayout |
LinearLayout.LayoutParams | Layout info associated with a child of a LinearLayout |
TableLayout.LayoutParams | Layout info associated with a child of a TableLayout |
RelativeLayout.LayoutParams | Layout info associated with a child of a RelativeLayout |
EXAMPLE 3.6 shows a very simple example showing how we can use class B
inside another class, Test
. To reference an inner class, we use the following syntax:
OuterClassName.InnerClassName
At line 3, we use this syntax to declare and instantiate b
, an object reference of type A.B
.
The output of Example 3.6 is:
number: 20
If B
was declared as a private
class, we could only use B
inside class A
and not outside it.
And if B
was not declared static
, we could not use the syntax A.B
.
If C
, an inner class of class A
, is declared public
but not static
, we could declare and instantiate an object reference of class C
as follows (using default constructors in this example):
A a = new A( ); A.C c = a.new C( );
We want to improve Version 2 by showing the current state of the game at the bottom of the screen, as shown in FIGURE 3.9.
The feedback message depends on the state of the game. It makes sense to let the TicTacToe
class, our Model, define the message. EXAMPLE 3.7 shows the result
method (lines 87–94) of the TicTacToe
class. It returns a String
that reflects the state of the game.
The GridLayout
used in Version 2 suits the layout of TicTacToe or any View
that requires a two-dimensional grid of GUI components very well. Sometimes we want to use a grid and we want to have the flexibility of combining several cells of the grid and place a single component there. The GridLayout
class enables us to span widgets over several rows or columns, or both, in order to achieve a more customized look and feel.
In Version 3, we use a GridLayout
with four rows and three columns. We place the buttons in the first three rows, and use the fourth row to place a TextView
across the three columns to display the game status.
The ViewGroup.LayoutParams
class enables us to specify how a View
child is to be positioned inside its parent layout. GridLayout.LayoutParams
inherits from the ViewGroup.
LayoutParams
class and we can use it to set the layout parameters of a GUI component before we add it to a GridLayout
.
A GridLayout.LayoutParams
object is defined by two components, rowSpec
and columnSpec
, both of type GridLayout.Spec
. Spec
is defined as a public static
inner class of GridLayout
. Thus, we refer to it using GridLayout.Spec
. RowSpec
defines a vertical span, starting at a row index and specifying a number of rows; columnSpec
defines a horizontal span, starting at a column index and specifying a number of columns. Thus, by defining rowSpec
, the vertical span, and columnSpec
, the horizontal span, we define a rectangular area within the grid where we can place a View
.
We can use the static spec
methods of the GridLayout.Spec
class to create and define a GridLayout.Spec
object, which defines a span. Once we have defined two GridLayout.Spec
objects, we can use them to define a GridLayout.LayoutParams
object. TABLE 3.7 shows these methods.
TABLE 3.7 Methods of the GridLayout
and GridLayout.LayoutParams
classes
Selected Methods of the GridLayout class |
---|
|
Constructor of the GridLayout.LayoutParams class |
|
FIGURE 3.7 shows a 4 × 6 grid of cells. The shaded area can be defined as follows:
The vertical span starts at index 1 and has size 2
The horizontal span starts at index 2 and has size 4
We could define a GridLayout.LayoutParams
object for the shaded area as follows:
// vertical span GridLayout.Spec rowSpec = GridLayout.spec( 1, 2 ); // horizontal span GridLayout.Spec columnSpec = GridLayout.spec( 2, 4 ); GridLayout.LayoutParams lp = new GridLayout.LayoutParams( rowSpec, columnSpec );
Note that we could use Spec
instead of GridLayout.Spec
if we include the following import statement:
import android.widget.GridLayout.Spec;
in addition to:
import android.widget.GridLayout;
FIGURE 3.8 shows a 4 × 3 grid of cells like the one in our app. The shaded area can be defined as follows:
The vertical span starts at index 3 and has size 1
The horizontal span starts at index 0 and has size 3
We define a GridLayout.LayoutParams
object for the shaded area as follows:
GridLayout.Spec rowSpec = GridLayout.spec( 3, 1 ); GridLayout.Spec columnSpec = GridLayout.spec( 0, 3 ); GridLayout.LayoutParams lp = new GridLayout.LayoutParams( rowSpec, columnSpec );
We use code similar to the one above in EXAMPLE 3.8 at lines 48–53 to define the layout parameters of status
. At line 54, we set them by calling setLayoutParams
. At line 34, we set the number of rows of our GridLayout
to four so that we can place status
in the fourth row. At lines 57–58, we set the height and width of status
so that it fills completely the area of the grid defined by its layout parameters. Note that it is possible to set the height and width of a component to be different from its layout parameters. We can then use the spec
method with three parameters to set the alignment of the component with respect to its defined layout parameters. Table 3.7 shows some possible values for the third parameter of the spec
method.
At lines 59–62, we center the text in status
, set its background color to green, set its text font size, and set its text content based on the state of tttGame
, respectively.
We add code to the update
method at lines 77 and 79 to change the color and text of status
when the game is over.
FIGURE 3.9 shows the app running inside the emulator, including the status of the game at the bottom of the screen.
In Version 4, we enable the player to play another game after the current one is over. When the game is over, we want a dialog box asking the user if he or she wants to play again to pop up. If the answer is yes, he or she can play again. If the answer is no, we exit the activity (in this case the app since there is only one activity).
The AlertDialog.Builder
class, part of the android.app
package, provides the functionality of a pop-up dialog box. It offers several choices to the user and captures the user’s answer. A dialog box of type AlertDialog.Builder
can contain up to three buttons: negative, neutral, and positive buttons. In this app, we only use two of them: the positive and negative buttons. Typically, these two buttons correspond to yes or no answers from the user, although that is not required. TABLE 3.8 shows the AlertDialog.Builder
constructor and other methods of that class.
TABLE 3.8 Selected constructor and methods of the AlertDialog.Builder
class
Constructor of the AlertDialog.Builder class |
|
Methods of the AlertDialog.Builder class |
|
EXAMPLE 3.9 shows the updated MainActivity
class. Inside the method showNewGame-Dialog
(lines 98–106), we declare and instantiate alert
, an AlertDialog.Builder
object at line 99, passing this
, the current MainActivity
object. MainActivity
inherits from Activity
, which inherits from Context
; thus, this
“is a” Context
object. In order to show the dialog box, we call the show
method at line 105. If we do not call the show
method, the dialog box does not show. However, showing a dialog box without buttons enabling interaction with the user results in a dialog box with a title and a message that cannot be closed. In this app, we want to ask the user if he or she wants to play another game, so we include only the positive and negative buttons.
The neutral, positive, or negative buttons can be included by calling the setPositiveButton
, setNegativeButton
, and setNeutralButton
methods of AlertDialog.Builder
. These three methods take two parameters. The first one is either a CharSequence
or an int
resource id representing the String
to be displayed inside the button. The second one is an object of type DialogInterface.OnClickListener
, an interface.
Thus, we need to implement the DialogInterface.OnClickListener
interface in order to declare and instantiate an object of that type and pass it as the second argument of these three methods. We implement it as a private
class in order to have access to the instance variables and methods of our MainActivity
class, in particular the object tttGame
and the methods to reset the Buttons
and the TextView
to their original state. The private
class PlayDialog
(lines 117–128) implements DialogInterface.OnClickListener
.
The DialogInterface.OnClickListener
interface contains one abstract
method, onClick
, which we override at lines 118–127. The onClick
method’s second parameter, id
, of type int
, contains information about what button was clicked by the user. If it is –1, the user clicked the positive button. If it is –2, the user clicked the negative button.
At line 119, we test if the value of id
is –1. If it is, we reset the instance variables of tttGame
to their starting values by calling resetGame
with tttGame
(line 120), we enable the buttons at line 121, clear them of any text at line 122, and update the background color and text in status
at lines 123–124. The enableButtons
and resetButtons
methods, coded at lines 86–90 and 92–96, enable or disable the nine buttons and reset their text content to the empty String
, respectively. If the value of id is –2, we exit the activity by calling the finish
method at line 126. Note that the expression this.finish()
would be incorrect, because this would refer to the current PlayDialog
object since we are inside that class. Because we want to call the finish
method with the current object of the MainActivity
class, we use MainActivity.this
to access it.
FIGURE 3.10 shows the app at the end of the game with the status reflecting that player 1 won, and asking the user to play again.
In Version 5, we split the View and the Controller. In this way, we make the View reusable. The Controller is the middleman between the View and the Model, so we keep the View independent from the Model.
In the View, in addition to the code creating the View, we also provide methods to:
▸ Update the View.
▸ Get user input from the View.
This is similar to the Model class, which provides methods to retrieve its state and update it.
In the Controller, in addition to an instance variable from the Model, we add an instance variable from the View. With it, we can call the various methods of the View to update it and get user input from it.
The array of buttons and the TextView
status are now inside the View. Updating the View means updating the buttons and the TextView
status. To that end, we provide the following methods:
▸ A method to set the text of a particular button
▸ A method to set the text of the TextView status
▸ A method to set the background color of the TextView status
▸ A method to reset the text of all the buttons to the empty String
(we need it when we start a new game)
▸ A method to enable or disable all the buttons (we also need it when we start a new game)
User input for this View is clicking on one of the buttons. Typically, event handling is performed in the Controller. Thus, we provide a method to check if a particular button was clicked.
EXAMPLE 3.10 shows the ButtonGridAndTextView
class, the View for Version 5 of this app. In Version 4, our View was a GridLayout
, thus, the ButtonGridAndTextView
class extends GridLayout
(line 10), and therefore, it “is” a GridLayout
. We have three instance variables (lines 11–13):
▸ buttons
, a two-dimensional array of Buttons
▸ status
, a TextView
▸ side
, an int
, the number of rows and columns in buttons
Because we want to keep the View independent from the model, we do not use the SIDE
constant of the TicTacToe
class to determine the number of rows and columns in buttons
. Instead, the side
instance variable stores that value. The constructor includes a parameter, newSide
, that is assigned to side
(lines 15–16 and 18). When we create the View from the Controller, we have access to the Model, thus, we will pass the SIDE
constant of the TicTacToe
class so that it is assigned to side
. The ButtonGridAndTextView
constructor includes three more parameters: a Context
, an int
, and a View.OnClickListener
. The Context
parameter is needed to instantiate the widgets of the View (the Buttons
and the TextView
). Since the Activity
class inherits from Context
, an Activity
“is a” Context
. Thus, when we create the ButtonGridAndTextView
from the Controller, we can pass this for the Context
parameter. We pass that Context
parameter to the Button
and TextView
constructors at lines 27 and 35. The int
parameter represents the width of the View. By having the width as a parameter, we let the Activity
client determine the dimensions of the View. We assign the newSide
parameter to side
at line 18. Finally, the View.OnClickListener
parameter enables us to set up event handling. We want to handle events in the Controller but the Buttons
are in the View, so event handling needs to be set up in the View. We do that at line 29. The constructor code can be made more robust by testing if newSide
and width
are positive. This is left as an exercise.
At lines 30 and 49, we add each Button
and the TextView
to this ButtonGridAndTextView
.
The setStatusText
, setStatusBackgroundColor
, setButtonText
, resetButtons
, and enableButtons
methods provide the ability to a client of the View (the Controller) to update the View. The isButton
method (lines 64–66) enables a client of the View (the Controller) to compare a Button
with a Button
from the array buttons
identified by its row and column. From the Controller, we will call that method to identify the row and the column of the Button
that was clicked.
EXAMPLE 3.11 shows the updated MainActivity
class. A ButtonGridAndTextView
instance variable, tttView
, is declared at line 14 and instantiated at line 24. Inside onClick
(lines 40–58), we first identify which button was clicked at line 43, and call setButtonText
to update the View at lines 46 and 48 depending on whose turn it is to play. If the game is over (line 49), we update the View accordingly by calling the setStatusBackgroundColor
and setStatusText
methods at lines 50 and 52. We also disable the buttons at line 51.
By separating the View from the Controller, we make the View reusable.
To create a GUI, we can either use XML or do it programmatically. For some apps, the number of widgets is dynamic and we have to do it programmatically. For some other apps, although we could do it using XML, it may be more convenient to do it programmatically.
The Android framework provides layout managers to help us organize a View
.
Layout managers are subclasses of ViewGroup
.
The addView
method adds a child View
to a ViewGroup
.
Using the methods getWindowManager
and getDefaultDisplay
, we can retrieve the characteristics of the display of the current device, in particular its width and height.
We can use the screen dimensions to properly size GUI components so that the look and feel is device-independent.
We can implement the View.OnClickListener
interface to handle a click event on a View
, for example a Button
. If we do, we need to implement its onClick
method.
We can specify layout parameters for a View
so that it is properly positioned when the View
is added to its ViewGroup
parent.
A GridLayout
places its children in a rectangular grid.
A GridLayout
offers the flexibility to span components over several rows or columns.
We can use the AlertDialog.Builder
class and the DialogInterface.
OnClickListener
interface to build an alert box.
We can call the finish
method to close an activity.
How do we import View.OnClickListener (if we want to use code like View. OnClickListener listener;)?
Importing the View class is sufficient
It is automatically imported
We must import View.OnClickListener;
We must import OnClickListener;
What is the name of the abstract method of View.OnClickListener?
listen
click
onClick
clickListen
What method of the ViewGroup class do we use to add a child View to a parent View?
addChild
addView
moreView
newView
We are coding inside the private class Y, which is coded inside the public class X. How do we access the current object of the Y class?
this
X.this
Y.this
this.X
We are coding inside the private class Y, which is coded inside the public class X. How do we access the current object of the X class?
this
X.this
Y.this
this.X
How do we retrieve the size of the screen (assuming that size is a Point object reference)?
getWindowManager( ).getSize( );
getDefaultDisplay( ).getSize( );
getWindowManager( ).getDefaultDisplay( ).getSize( size );
getWindowManager( ).getDefaultDisplay( ).getSize( );
What is the data type of this in the code GridLayout gridLayout = new GridLayout( this )?
GridLayout
Context
Grid
View
What method of the GridLayout class do we use to set the number of rows of the grid?
setRows
setRowCount
setCount
numberOfRows
What method of the Activity class do we use to set the view for an activity?
setLayout
setContentView
setView
view
Inside an Activity class, how do we instantiate a button?
Button b =
new Activity( );
Button b =
new Button( );
Button b =
new Button( this );
Button b =
new Button( Activity );
What class is used by views to tell their parents how they want to be laid out?
Layout
Params
ViewParams
LayoutParams
What method do we use to specify the alignment of the text within a TextView?
setAlignment
center
setGravity
align
Assign the width of the current screen to a variable name width.
// Your code goes here
This code creates a GridLayout within the current context and sets its number of rows to four and its number of columns to two.
// Your code goes here
This code creates a button within the current context.
// Your code goes here
This code creates a 5 × 2 two-dimensional array of buttons within the current context.
// Your code goes here
This code adds a Button object named b, specifying its width and height as 200 pixels each, to an already created GridLayout object named gl.
// Your code goes here
This code defines a GridLayout.LayoutParams object for the shaded area below.
// Your code goes here
This code defines a GridLayout.LayoutParams
object for the shaded area below.
// Your code goes here
This code sets up an alert view for the current activity. It sets its title to HELLO and its message to HAVE FUN
AlertDialog.Builder alert = new AlertDialog.Builder( this ); // Your code starts here PlayDialog pd = new PlayDialog( ); alert.setPositiveButton( ”YES”, pd ); alert.setNegativeButton( ”NO”, pd ); // And continues here so that the alert view shows
This code checks if the button that was clicked is a button named b. If it is, it outputs to Logcat YES, otherwise, it outputs to Logcat NO.
private class ButtonHandler implements View.OnClickListener { public void onClick( View v ){ // Your code goes here } }
Write an app that displays one label and one button. Every time the user clicks on the button, the label toggles between being visible and not being visible. Do not use XML.
Write an app that has three labels and one button. The three labels represent a traffic light. When the app starts, only the label with the red background shows. When the user clicks on the button, only the label with the yellow background shows. When the user clicks again on the button, only the label with the green background shows. When the user clicks again on the button, only the label with the red background shows . . . and the cycle continues. Do not use XML.
Write an app that displays one label and one button. The label represents the current color of a traffic light and can be red, yellow, or green. When the user clicks on the button, the label cycles to the next color and simulates running the traffic light. Do not use XML.
Write an app that displays one label and one button. Every time the user clicks on the button, the label moves down by 10 pixels. When the label reaches the bottom of the screen, it no longer moves when the user clicks on the button. Do not use XML.
Write an app that displays two labels and one button. The first label should display a randomly generated integer between 50 and 100. When the user clicks on the button, the second label moves to a position whose y-coordinate is the integer displayed in the first label. Do not use XML.
Write an app that has one text field, one label, and one button. The user can enter his or her email in the text field. When the user clicks on the button, the app checks if the email entered contains the @ character and a dot somewhere after the @ character. If it does, the label displays VALID, otherwise it displays INVALID in the label. The text field and the label should have their own separate style, each with a minimum of four attributes. Include a Model. Do not use XML.
Write an app that has one text field and one label. The user can enter his or her password. The label displays, as the user types, WEAK or STRONG. For the purpose of this app, we define a weak password as having eight or fewer characters. A strong password is defined as having nine or more characters. Include a Model. Do not use XML.
Write an app that displays a chessboard on a grid with black and white pieces. You can represent each piece by a letter or two, for example, P for pawn, Q for queen, R for rook, K for knight, B for bishop, and KG for king. When the user clicks on a knight, color in green the cells where the knight can move. Include a Model. Do not use XML.
Write an app that displays four labels and a button. The first two labels represent two cards in a simplified blackjack game and are filled with two randomly generated integers with values between 1 and 11 inclusive. The total of the two is displayed inside the fourth label. When the user clicks on the button, if the current total shown inside the fourth label is 15 or less, the third label is filled with a randomly generated number between 1 and 11 inclusive and the total inside the fourth label is updated to equal the sum of the three numbers in labels one, two, and three. If the current total shown inside the fourth label is greater than 15, nothing happens. Include a Model. Do not use XML.
18.188.254.131