0% found this document useful (0 votes)
12 views84 pages

Ch3 PTUDTT

The document discusses programmatically creating the GUI for a Tic-Tac-Toe game app. It introduces layout managers and describes creating a 3x3 grid of buttons to represent the game board without using XML. It also covers retrieving screen dimensions and adding the buttons to a GridLayout programmatically.

Uploaded by

trandinhloi99
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views84 pages

Ch3 PTUDTT

The document discusses programmatically creating the GUI for a Tic-Tac-Toe game app. It introduces layout managers and describes creating a 3x3 grid of buttons to represent the game board without using XML. It also covers retrieving screen dimensions and adding the buttons to a GridLayout programmatically.

Uploaded by

trandinhloi99
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 84

CHAPTER THREE:

Coding the GUI


Programmatically,
Layout Managers

Chapter opener image: © Fon_nongkran/Shutterstock


CHAPTER CONTENTS
Introduction

3.1 Model View Controller


Architecture

3.2 The Model

3.3 Creating the GUI


Programmatically, TicTacToe,
Version 0

3.4 Event Handling: TicTacToe,


Version 1

3.5 Integrating the Model to


Enable Game Play: TicTacToe,
Version 2

3.6 Inner Classes

3.7 Layout Parameters:


TicTacToe, Version 3

3.8 Alert Dialogs: TicTacToe,


Version 4

3.9 Splitting the View and the


Controller: TicTacToe, Version 5

Chapter Summary

Exercises, Problems, and Projects


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[ ][ 3 × 3 two-dimensional array representing the


] game board

private int 1 = player 1’s turn to play; 2 = player 2’s turn to


turn play

Constructor

TicTacToe( ) Constructs a TicTacToe object; clears the


board and prepares to play by setting turn to 1

Public Methods

int play( int If the play is legal and the board cell is not
row, int taken, plays and returns the old value of turn
column )

boolean Returns true if there is still at least one cell not


canStillPlay( taken on the board; otherwise, returns false
)

int whoWon( Returns i if player i won, 0 if nobody has won


) yet

boolean Returns true if the game is over, false


gameOver( ) otherwise

void Clears the board and sets turn to 1


resetGame( )
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 Package Method and fields


Interface

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( Constructs a GridLayout within the app


Context context ) environment defined by context.

Public Methods

setRowCount( int Sets the number of rows in the grid to


rows ) rows.

setColumnCount( Sets the number of columns in the grid to


int cols ) cols.

addView( View Method inherited from ViewGroup; adds


child, int w, int h ) 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
COMMON ERROR: We must instantiate a
GUI component before using it. In
particular, adding a GUI component that
has not been instantiated to a View will
result in a NullPointerException .

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 Called when a View has been clicked; the


void onClick( parameter v is a reference to that View.
View v )

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
COMMON ERROR: When overriding an
abstract method of an interface, it is
mandatory that the method header be the
same, minus the keyword abstract , as the
method header of the listener interface.
Otherwise, the method will not override the
abstract method of the interface, and that
will generate a compiler error.

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

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

Returns a GridLayout.Spec object where start is the


starting index and size is the size.

public static GridLayout.Spec spec( int start, int size,


GridLayout.Alignment alignment )

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

GridLayout.LayoutParams( Spec rowSpec, Spec


columnSpec )

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

public AlertDialog.Builder( Context context )

Constructs an AlertDialog.Builder dialog box object


for the context parameter.

Methods of the AlertDialog.Builder class

public AlertDialog.Builder setMessage( CharSequence


message )

Sets the message to display to message; returns


this AlertDialog.Builder object, which allows
chaining if desired.

public AlertDialog.Builder setTitle( CharSequence title )

Sets the title of the alert box to title; returns this


AlertDialog.Builder object, which allows method call
chaining if desired.

public AlertDialog.Builder setPositiveButton(


CharSequence text, DialogInterface.OnClickListener
listener )

Sets listener to be invoked when the user clicks on


the positive button; sets the text of the positive
button to text.

public AlertDialog.Builder setNegativeButton(


CharSequence text, DialogInterface.OnClickListener
listener )

Sets listener to be invoked when the user clicks on


the negative button; sets the text of the negative
button to text.
public AlertDialog.Builder setNeutralButton(
CharSequence text, DialogInterface.OnClickListener
listener )

Sets listener to be invoked when the user clicks on


the neutral button; sets the text of the neutral button
to text.

public AlertDialog show( )

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.
COMMON ERROR: Inside a private class,
the keyword this refers to the current
object of the private class, not to the current
object of the public class that contains the
private class. In order to access the
current object of class A from inside its
inner class B, do not use this but use
A.this instead.

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
13. Assign the width of the current screen to a
variable name width.

// Your code goes here

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

15. This code creates a button within the current


context.

// Your code goes here

16. This code creates a 5 × 2 two-dimensional


array of buttons within the current context.

// Your code goes here


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

18. This code defines a


GridLayout.LayoutParams object for the
shaded area below.

// Your code goes here


19. This code defines a GridLayout.LayoutParams

object for the shaded area below.

// Your code goes here

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

21. 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
22. 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.
23. 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.
24. 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.
25. 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.
26. 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.
27. 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.
28. 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.
29. 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.
30. 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.

You might also like