CHAPTER THREE: Coding the GUI Programmatically, Layout Managers

Chapter opener image: © Fon_nongkran/Shutterstock

Introduction

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.

3.1 Model View Controller Architecture

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.

3.2 The Model

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

EXAMPLE 3.1 The TicTacToe class

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.

3.2 Creating the GUI Programmatically, TicTacToe, Version 0

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.

FIGURE 3.1 Inheritance hierarchy showing some layout classes

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.

EXAMPLE 3.2 The MainActivity class, TicTacToe app Version 0

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.

FIGURE 3.2 Inheritance hierarchy showing the Context and Activity classes

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.

FIGURE 3.3 TicTacToe app, Version 0, running inside the emulator

3.4 Event Handling: TicTacToe, Version 1

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:

  1. Write an event handler (a class extending a listener interface)

  2. Instantiate an object of that class

  3. 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.

EXAMPLE 3.3 The MainActivity class, TicTacToe app, Version 1

FIGURE 3.4 TicTacToe app, Version 1, running inside the emulator

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.

FIGURE 3.5 Partial output of Logcat

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.

3.5 Integrating the Model to Enable Game Play: TicTacToe, Version 2

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).

EXAMPLE 3.4 The MainActivity class, TicTacToe app, Version 2

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.

3.6 Inner Classes

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.

FIGURE 3.6 TicTacToe app, Version 2, running inside the emulator—player 1 just won

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.5 B is a public, static, inner class of class A

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.

EXAMPLE 3.6 Using B, a public, static, inner class of class A in another class

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( );

3.7 Layout Parameters: TicTacToe, Version 3

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.

EXAMPLE 3.7 The result method of the TicTacToe class

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
  1. public static GridLayout.Spec spec( int start, int size )

    1. Returns a GridLayout.Spec object where start is the starting index and size is the size.

  2. public static GridLayout.Spec spec( int start, int size, GridLayout.Alignment alignment )

    1. Returns a GridLayout.Spec object where start is the starting index and size is the size.

      Alignment is the alignment; common values include GridLayout.TOP,

      GridLayout,BOTTOM, GridLayout.LEFT, GridLayout.RIGHT, GridLayout.CENTER.

Constructor of the GridLayout.LayoutParams class
  1. GridLayout.LayoutParams( Spec rowSpec, Spec columnSpec )

    1. Constructs a LayoutParams object with rowSpec and columnSpec.

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

FIGURE 3.7 An area of a 4 × 6 grid defined by a vertical span with start = 1 and size = 2, and a horizontal span with start = 2 and size = 4

FIGURE 3.8 An area of a 4 × 3 grid defined by a vertical span with start = 3 and size = 1, and a horizontal span with start = 0 and 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.

EXAMPLE 3.8 The MainActivity class, TicTacToe app, Version 3

FIGURE 3.9 TicTacToe app, Version 3, running inside the emulator

FIGURE 3.9 shows the app running inside the emulator, including the status of the game at the bottom of the screen.

3.8 Alert Dialogs: TicTacToe, Version 4

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
  1. public AlertDialog.Builder( Context context )

    1. Constructs an AlertDialog.Builder dialog box object for the context parameter.

Methods of the AlertDialog.Builder class
  1. public AlertDialog.Builder setMessage( CharSequence message )

    1. Sets the message to display to message; returns this AlertDialog.Builder object, which allows chaining if desired.

  2. public AlertDialog.Builder setTitle( CharSequence title )

    1. Sets the title of the alert box to title; returns this AlertDialog.Builder object, which allows method call chaining if desired.

  3. public AlertDialog.Builder setPositiveButton( CharSequence text, DialogInterface.OnClickListener listener )

    1. Sets listener to be invoked when the user clicks on the positive button; sets the text of the positive button to text.

  4. public AlertDialog.Builder setNegativeButton( CharSequence text, DialogInterface.OnClickListener listener )

    1. Sets listener to be invoked when the user clicks on the negative button; sets the text of the negative button to text.

  5. public AlertDialog.Builder setNeutralButton( CharSequence text, DialogInterface.OnClickListener listener )

    1. Sets listener to be invoked when the user clicks on the neutral button; sets the text of the neutral button to text.

  6. public AlertDialog show( )

    1. Creates, returns an AlertDialog box and shows it.

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.

EXAMPLE 3.9 The MainActivity class, TicTacToe app, Version 4

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.

3.9 Splitting the View and the Controller: TicTacToe, Version 5

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)

FIGURE 3.10 TicTacToe app, Version 4, running inside the emulator, showing the alert dialog box at the end of a 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

EXAMPLE 3.10 The ButtonGridAndTextView class, TicTacToe app, Version 5

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.

EXAMPLE 3.11 The MainActivity class, TicTacToe app, Version 5

By separating the View from the Controller, we make the View reusable.

Chapter Summary

  • 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.

Exercises, Problems, and Projects

Multiple-Choice Exercises

  1. 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;

  2. What is the name of the abstract method of View.OnClickListener?

    • listen

    • click

    • onClick

    • clickListen

  3. What method of the ViewGroup class do we use to add a child View to a parent View?

    • addChild

    • addView

    • moreView

    • newView

  4. 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

  5. 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

  6. 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( );

  7. What is the data type of this in the code GridLayout gridLayout = new GridLayout( this )?

    • GridLayout

    • Context

    • Grid

    • View

  8. What method of the GridLayout class do we use to set the number of rows of the grid?

    • setRows

    • setRowCount

    • setCount

    • numberOfRows

  9. What method of the Activity class do we use to set the view for an activity?

    • setLayout

    • setContentView

    • setView

    • view

  10. 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 );

  11. What class is used by views to tell their parents how they want to be laid out?

    • Layout

    • Params

    • ViewParams

    • LayoutParams

  12. What method do we use to specify the alignment of the text within a TextView?

    • setAlignment

    • center

    • setGravity

    • align

Fill in the Code

  1. Assign the width of the current screen to a variable name width.

    // Your code goes here
    
  2. 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
    
  3. This code creates a button within the current context.

    // Your code goes here
    
  4. This code creates a 5 × 2 two-dimensional array of buttons within the current context.

    // Your code goes here
    
  5. 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
  6. This code defines a GridLayout.LayoutParams object for the shaded area below.

    // Your code goes here
    
  7. This code defines a GridLayout.LayoutParams object for the shaded area below.

    // Your code goes here
    
  8. 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
    
  9. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

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

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