Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Application Programming
Challenge: Tic-Tac-Toe App
Introduction
The goal of this assignment is to create a simple tic-tac-toe game for Android
as illustrated on the right. You will use buttons to represent the game board
and a text control to display the status of the game at the bottom of the
screen. The buttons will have no text on them when the game starts, but
when the user clicks on a button, it will display a green X. The computer will
them move and turn the appropriate button text to a red O. The text control
will indicate whose turn it is and when the game is over.
App Design
Rather than starting from scratch, a fully functional tic-tac-toe game
that runs in a console window has been provided for you at:
http://cs.harding.edu/fmccown/android/TicTacToeConsole.java
Run this program and take a look at the source code to familiarize yourself with how it works. It uses a char
array to represent the game board with X representing the human and O representing the computer. The
program implements a simple AI for the computer: it will win the game if possible, block the human from
winning, or make a random move. The game has all the logic to switch between the two players and to check
for a winner.
Our goal is to convert this console game into an Android game. We have two basic choices when porting this
game to Android: we could merge the game logic with the user interface (UI) code, or we could keep the two
separated as much as possible. The second approach is preferred since it allows us to make changes to the UI,
like changing the layout of the board, without having to modify code that just deals with game logic. Also if we
decide to port our game to another platform like the BlackBerry or iPhone, having our UI and game logic cleanly
separated will minimize the amount of code needing to be modified and make the port much easier to perform.
/** Set the given player at the given location on the game board.
* The location must be available, or the board will not be changed.
*
* @param player - The HUMAN_PLAYER or COMPUTER_PLAYER
* @param location - The location (0-8) to place the move
*/
public void setMove(char player, int location)
/** Return the best move for the computer to make. You must call setMove()
* to actually make the computer move to that location.
* @return The best move for the computer to make (0-8).
*/
public int getComputerMove()
/**
* Check for a winner and return a status value indicating who has won.
* @return Return 0 if no winner or tie yet, 1 if it's a tie, 2 if X won,
* or 3 if O won.
*/
public int checkForWinner()
5. For the game logic to be accessible to the AndroidTicTacToeActivity, create a class-level variable for the
TicTacToeGame class in AndroidTicTacToeActivity.java:
The XML below places buttons 1 through 3 into a TableRow and a TextView below the grid. The TextView
will be used later to indicate whose turn it is. It is left to you to define buttons 4 through 9 which should be
placed in their own TableRows.
<TableLayout
android:id="@+id/play_grid" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_marginTop="5dp" >
<TableRow android:gravity="center_horizontal">
<Button android:id="@+id/one" android:layout_width="100dp"
android:layout_height="100dp" android:text="1"
android:textSize="70dp" />
</LinearLayout>
Notice that each Button has several attributes:
• id – a unique ID which will be used in our Java code to access the widget. The @ tells the XML parser to
expand the rest of the ID string and identify it as an ID resource. The + means this is a new resource
name that must be added to the app’s resources in the R.java file.
• layout_width – the width of the widget in density-independent pixels (dp). These allow the buttons to
scale appropriately for Android devices with different resolutions. The TextView uses a layout_width
value of fill_parent which means the widget should fill the entire parent. Another acceptable value
would be wrap_content which makes the widget just big enough to hold the content.
• layout_height – the height of the widget in pixels or fill_parent or wrap_content.
• text – the text that should be displayed in the button.
• textSize – the font size of the text. This can be defined with pixels (px), inches (in), millimeters (mm),
points (pt), density-independent pixels (dp), or scale-independent pixels (sp – like dp but scaled by the
user's font size preference). Using dp is usually best practice.
The TextView uses these additional properties:
• gravity – specifies how the text should be placed within its container. The center_horizontal value
centers the text horizontally.
• layout_marginTop – how much of a margin should be left on the top side of the widget.
Accessing the Widgets
We now need to connect the buttons and text widgets defined in main.xml with our Activity.
1. In AndroidTicTacToeActivity.java, declare a Button array and TextView member variables:
// Buttons making up the board
private Button mBoardButtons[];
mGame.clearBoard();
2. To clear the visible game board, we need to loop through all the buttons representing the board and set
their text to an empty string. We also need to enable each button (we will disable them later when a move
is placed), and we to create an event listener for each button by setting each button’s OnClickListener to
a new ButtonClickListener, a class which we will define shortly. We pass to ButtonClickListener’s
constructor the button’s number (0-8) which will be used to identify which button was actually clicked on.
Add the code below to the startNewGame() function.
// Reset all buttons
for (int i = 0; i < mBoardButtons.length; i++) {
mBoardButtons[i].setText("");
mBoardButtons[i].setEnabled(true);
mBoardButtons[i].setOnClickListener(new ButtonClickListener(i));
}
3. Finally, we will indicate that the human is to go first. Note that the “You go first.” text should normally not
be hard-coded into the Java source code for a number of reasons, but fix this later. Add these lines to the
bottom of startNewGame().
} // End of startNewGame
4. You need to call startNewGame() when the App first loads, so add this call on the last line of onCreate():
@Override
public void onCreate(Bundle savedInstanceState) {
...
startNewGame();
}
5. Now let’s add the ButtonClickListener that was used earlier when resetting the buttons. It needs to
implement the android.view.View.OnClickListener interface which means it needs to implement an
onClick() method. We also need to create a constructor that takes an integer parameter, the button
number, since we need to know which button was clicked on. Without this information, we won’t be able to
easily determine which button was selected and where the X should be placed.
The onClick() method is called when the user clicks on a button. We should only allow the user to click on
enabled buttons which represent valid locations the user may place an X. We will write a setMove()
function shortly which will place the X or O at the given location and disable the button. After displaying the
human’s move, we need to see if the human has won or tied by calling checkForWinner(). If this function
returns back 0, then the game isn’t over, so the computer must now make its move. Once it moves, we
must again check to see if the game is over and update the information text appropriately.
// Handles clicks on the game board buttons
private class ButtonClickListener implements View.OnClickListener {
int location;
if (winner == 0)
mInfoTextView.setText("It's your turn.");
else if (winner == 1)
mInfoTextView.setText("It's a tie!");
else if (winner == 2)
mInfoTextView.setText("You won!");
else
mInfoTextView.setText("Android won!");
}
}
}
mGame.setMove(player, location);
mBoardButtons[location].setEnabled(false);
mBoardButtons[location].setText(String.valueOf(player));
if (player == TicTacToeGame.HUMAN_PLAYER)
mBoardButtons[location].setTextColor(Color.rgb(0, 200, 0));
else
mBoardButtons[location].setTextColor(Color.rgb(200, 0, 0));
}
7. Now run your app and play a game. You should see the a message at the bottom of the board telling you
when it’s your turn, but the computer moves so quickly that you will likely never see the message indicating
it’s the computer’s turn. When the game is over, you won’t be able to play another game unless you restart
the app. This is a problem we’ll fix in the next section.
8. Another problem is that when the game is over, the human can continue making moves! You will need to fix
this problem by introducing a class-level boolean mGameOver variable to AndroidTicTacToeActivity.
When the game starts, mGameOver should be set to false. When the user is clicking on a button, the
variable should be checked so a move isn’t made when the game is over. And mGameOver needs to be set to
true when the game has come to an end. It’s left to you to make the appopriate modifications.
Adding a Menu
In order to start a new game, let’s add an option menu so that when the user clicks on the device’s Menu
button, the New Game option appears as pictured below.
1. In AndroidTicTacToeActivity, override the onCreateOptionsMenu() method which is called only once,
when the Activity’s option menu is shown the first time. The code below creates a single menu option
labelled New Game. The function must return true for the menu to be displayed.
String Messages
Several times we have hard-coded UI string messages into our Java source code. For example, when the user
wins a game, we have hard-coded the response “You win!” This is a problem for a couple of reasons. First, we
may want to change the contents of this message at a later date, and it would be helpful if we did not have to
dig through our source code and recompile just to change this message. Secondly, it we wanted our game to
use messages in other languages (localization), hard-coding other languages in the source code is problematic.
We should instead follow best-practices and place all our UI messages in strings.xml, a file that is located in the
project’s res/values directory.
1. Locate strings.xml and double-click on it to view its contents in Eclipse.
2. Add the following strings to be used in your application:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tic-Tac-Toe</string>
<string name="first_human">You go first.</string>
<string name="turn_human">Your turn.</string>
<string name="turn_computer">Android\'s turn.</string>
<string name="result_tie">It\'s a tie.</string>
<string name="result_human_wins">You won!</string>
<string name="result_computer_wins">Android won!</string>
</resources>
A public, static, final identifier will be created for each of these messages in R.java. Note that a single
apostrophe (“It\’s”) in your string messages must be escaped by placing a backslash in front of it.
Extra Challenge
As currently implemented, the human always goes first. Make the game fairer by alternating who gets to go
first. Also keep track of how many games the user has won, the computer has won, and ties. You should display
this information using TextView controls under the game status TextView. Use the RelativeLayout class to
position the TextView controls next to each other
like the example on the right:
References
Jason Houle’s Tic Tac Toe for Android tutorial was helpful in producing this tutorial.
Except as otherwise noted, the content of this document is
licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0
Introduction
In the previous tutorial, you created a simple tic-tac-toe game for the Android.
The goal of this tutorial is to improve your game by creating a more
sofisticated menu that uses a few dialog boxes.In this tutorial you will create
an options menu (as shown on the right) with three options:
You should familiarize yourself with the official Menu Design Guidelines when
designing your own menus. Android also supports context menus, floating
lists of menu items that appear when holding your finger down on the screen
(a long press),but they will not be covered in this tutorial.
We are going to create a menu option in this tutorial to change the difficulty level of the game. Right now the
game only has one difficulty level which we’ll call “Expert”.You will need to modify the TicTacToeGame class so
the difficulty level can be set. When set to the “Easy” level, the computer will always just make random moves.
When set to the “Harder” level, the computer will make a winning move if possible, otherwise it will make a
random move.
1. Open TicTacToeGame.java, and add an enumeration for the difficulty level and a variable for keeping track
of the current difficulty level setting:
publicclassTicTacToeGame {
publicDifficultyLevelgetDifficultyLevel() {
returnmDifficultyLevel;
}
publicvoidsetDifficultyLevel(DifficultyLeveldifficultyLevel) {
mDifficultyLevel = difficultyLevel;
}
publicintgetComputerMove() {
if (mDifficultyLevel == DifficultyLevel.Easy)
move = getRandomMove();
elseif (mDifficultyLevel == DifficultyLevel.Harder) {
move = getWinningMove();
if (move == -1)
move = getRandomMove();
}
elseif (mDifficultyLevel == DifficultyLevel.Expert) {
return move;
}
Now we will add a menu to the Activity that will be used to start a new game, set the TicTacToeGame’s difficulty
level, and quit. Android can display up to six menu options at once, and a More option makes an expanded menu
available. We’ll only have three options in our menu. Instead of creating the menu options inside of your code
like we did in the previous assignment, it’s best-practice to create your menu via XML instead so changes can be
made to the menu without modifying any code.
<menuxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:id="@+id/new_game"
android:title="New Game"
android:icon="@drawable/new_game"/>
<itemandroid:id="@+id/ai_difficulty"
android:title="Difficulty"
android:icon="@drawable/difficulty_level"/>
<itemandroid:id="@+id/quit"
android:title="Quit"
android:icon="@drawable/quit_game"/>
</menu>
Now you’ll need to add the images that are used in the menu items to your project:
1. Create new_game.png, difficulty_level.png, and quit_game.png images using your favorite image editor.
Feel free to use images you find on the web (AndroidIcons.com has some free icons) orfrom the SDK (e.g.,
C:\ANDROID_SDK\platforms\android-2.1\data\res\drawable-mdpi). Make sure the images are not taller
than 42 pixels, or the images will obstruct the text in the menu items.If you want to create icons that
conform strictly to the Android platform, take a look at the official Android Icon Design Guidelineswhich give
very specific instructions for creating menu item icons, including instructions for creating three different
sizes of images for the various screen sizes Android may be running on.
2. Your project will have three folders under res called drawable-hdpi, drawable-ldpi, and drawable-mdpi.
These folders are for holding images that are best for different resolutions. We will not concern ourselves
with this issue right now. So create a folder called res/drawable, just like you did earlier when you created
res/menu. The images in this folder will be used regardless of the Android device’s screen resolution.
3. Drag the image files you created in step 1 on top of the drawable folder in Eclipse. This will require you to
have the file explorer window open in front of Eclipse drag and drop the files. Another way to add your
images to the project is to copy the images into the drawable folder using the file explorer and then select
FileRefresh in Eclipse.
4. You should now see your png images in the drawable folder. If you used different file names than were
specified in step 1, you should correct your options_menu.xml file so it uses the same names as your files.
Now you need to add code to your program to create the menu and respond to menu items selections:
1. Modify your code to load the options menu by modifying your onCreateOptionsMenu()function. Note how
R.menu.options_menu equates to the ID in options_menu.xml you created earlier.
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflaterinflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
returntrue;
}
Next you need to modify the onOptionsItemSelected()functionwhich is called when a menu option is
selected. You can tell which menu item was selected by examining the menu item’s ID which was given in
the options_menu.xml file. (showDialog is deprecated).
The menu you just added will display dialog boxes that correspond to some constants which we’ll define
next.
2. Create two constants which will be used to identify the two dialog boxes:
staticfinalintDIALOG_DIFFICULTY_ID = 0;
staticfinalintDIALOG_QUIT_ID = 1;
In the code below, there are two places marked “TODO” where the
code is incomplete. You willneed to set the selectedvariable so the
setSingleChoiceItems() function will properly show radio buttons labeled Easy, Harder, and Expert as
shown on the right. The currently selected difficulty level should initially be selected.
When one of the radio buttons is selected, the dialog will close, and the itemparameter of onClick will
indicate which radio button was selected. You will need to write the code to set the game’s internal
difficulty level. You will also need to add the appropriate string constants to strings.xml.
Note that each call to the builder object returns the reference to the builder object so method-chaining
becomes possible.
switch(id) {
caseDIALOG_DIFFICULTY_ID:
builder.setTitle(R.string.difficulty_choose);
finalCharSequence[] levels = {
getResources().getString(R.string.difficulty_easy),
getResources().getString(R.string.difficulty_harder),
getResources().getString(R.string.difficulty_expert)};
builder.setSingleChoiceItems(levels, selected,
newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dialog, int item) {
dialog.dismiss(); // Close dialog
// TODO: Set the diff level of mGamebased on which item was selected.
break;
}
return dialog;
}
The code above displays a Toast message (see Android.widget.Toast) that indicates which difficulty level
was selected from the dialog box. Toast messages appear for just a few seconds and are helpful for
presenting short, brief information to the user.
builder.setMessage(R.string.quit_question)
.setCancelable(false)
.setPositiveButton(R.string.yes, newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dialog, int id) {
AndroidTicTacToeActivity.this.finish();
}
})
.setNegativeButton(R.string.no, null);
dialog = builder.create();
break;
}
Note that most Android apps do not have a Quit option since clicking on the device’s Home button
essentially does the same thing.
Run your app and verify that the menu options work as expected.
Extra Challenge
<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
Now run your app on the emulator. Press the Home key and press the App Drawer button on the screen to list
all the apps installed. This is where you will see the icon you have created for your app. Mine is shown above.
2. Create a menu option that displays an About dialog box,similar the one
shown on the right, which identifies you as the programmer.
You can create your own custom dialog box by first creating a
res/layout/about_dialog.xmlfile with the dialog box components (just like
you did in main.xml). You’ll need to reference an image you create from the
res/drawablefolder in your XML file. In the onCreateDialog()method, use a
LayoutInflaterto expand about_dialog.xmlinto a View, attach the Viewto
an AlertDialog.Builder,and create the dialog. The code that does all this is
shown below.
Except as otherwise noted, the content of this document is licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0
Introduction
Your tic-tac-toe game only works in portrait mode. If you were to flip the emulator into landscape mode by
pressing Ctrl-F11, you would see the board is cleared of all the images, and the text beneath the board is not
visible. While we could force the user to only use our app in portrait mode, we should make our app more
flexible by allowing the user to play with it in either mode.
The goal of this tutorial is to show you how to develop an application that can work in portrait and landscape
mode. You will also learn how to make the application’s data persist when the orientation is changed and how
to make data persist between application invocations.
When you press Ctrl-F11, your emulator will switch from portrait mode to landscape mode. Pressing Crtl-F11
again switches it back. Your game was built for portrait mode, so it does not behave properly when switched to
landscape mode.
You can force Android to only show your app in portrait mode: just add android:screenOrientation=
"portrait" (or " landscape" if you wish) to the < activity> element in the app’s AndroidManifext.xml file.
However, best practice is to allow the user to interact with the app in either orientation.
Our goal is to create a landscaped orientation that looks like
the screenshot on the right.
1. To create more screen real estate, remove the title bar
from the application window by modifying the
application element in A ndroidManifest.xml:
3. Copy the res/layout/main.xml file into the layout-land directory. This is the file that Android will apply
to the Activity’s View when the emulator is put in landscape mode.
4. Open the layout-land/main.xml file and change the size of the BoardView to 270x270 dp to make it take
up less space. Also align it on the left side of the screen. To do this, you can change the L inearLayout’s
orientation property to h orizontal or replace the L inearLayout entirely with a R elativeLayout. You
will also need to change the layout of the T extView controls so they are displayed to the right of the game
board. It is left to you to make these edits. To test your layout without running your game in the
emulator, you can switch to Graphical Layout mode and set the orientation to Landscape as shown below.
5. After you have the BoardView and TextViews aligned properly, run your program and press Ctrl-F11 to
change its orientation. Your app should switch orientations and display all the screen elements correctly.
Try making a few moves, and before the game is over, press Ctrl-F11. What happens? When the orientation
is changed in the middle of the game, the board is cleared and your game starts over from the beginning. If
you play a few games and then change the orientation, you’ll notice that the scores are also reset to zero.
You can imagine how frustrated a user would get if they were about to win a game, they shifted their phone
a little which caused the orientation to change, and the game reset! Ideally we’d like our game to maintain
its state when changing orientation, so we’ll fix this problem next.
1
Figure is from Application Fundamentals at http://developer.android.com/guide/topics/fundamentals.html
Fortunately, Android provides a mechanism to remember whatever pieces of information we would like when
the Activity is started again after changing the device’s orientation. We first need to decide what information is
necessary to start our game back in the same state. We’ll bundle the information together before the Activity is
killed, and Android will pass it back to us in the o nCreate() method. We’ll extract the bundled information and
put it back into the necessary variables to pick up right where we left off.
1. To save the state of the game, you need to override the onSaveInstanceState() method for the
AndroidTicTacToe class (the Activity). This method is called by Android before it pauses the application
(before o nPause() is triggered), and it passes the method a B undle object which can be used to save
key/value pairs that will be given back to the application (via another call to o nCreate()) even if the Activity
is dropped from memory.
For the above code to work, you will need to create a getBoardState() function for the TicTacToeGame
class which returns a character array representing the state of the board. I t is left for you to implement
this method.
2. When onCreate() is called the first time, its parameter savedInstanceState will be null, and the game
should be started like usual. But after changing the orientation, o nCreate’s savedInstanceState will
contain the key/value pairs placed in o nSaveInstanceState‘s o utState bundle. You will need to extract
the values from the B undle and place them back into their associated variables like so:
3. The code above also calls a displayScores() function to show the scores. Declare it like so:
4. Note that there is also an onRestoreInstanceState() method that can be overridden for the Activity to
restore the application’s state. So instead of placing your restore code in the else statement in step 2, you
could do this:
5. Run your application and make some moves. When you press Ctrl-F11, the board should remain in the same
condition it was before. However, you’ll probably notice that the computer makes an additional move when
it’s your turn! This is because the variable that ensures it’s the correct person’s turn is not being saved. It’s
left to you to fix this bug.
If you were to click the Back button on the emulator and restart the application, either by clicking on the app’s
icon or using Eclipse to restart, your application’s state is not saved; your game board will be cleared and your
game scores (Human, Tie, Android) will be reset to 0. That’s because Android only saves state information (by
calling o nSaveInstanceState()) if Android is responsible for killing the Activity. If you kill it by clicking the
Back button, for example, then o nSaveInstanceState() is not called.
In order for information to persist between application restarts, there are several mechanisms to choose from.
You could store data to a file and read it back in, or you could save data to a SQLite database. If you only need to
store simple key/value pairs like we have been doing, the S haredPreferences is the ideal class to use.
Follow the instructions below to use the SharedPreferences class to make the game scores persist between
application restarts:
2. In the onCreate() method, use Activity.getSharedPreferences() to initialize mPrefs. The first
argument is the name of the preference file, and the second argument is typically MODE_PRIVATE or 0.
3. When your application is being terminated, the Activity’s onStop() method will be called (refer back to the
Activity life cycle figure shown earlier). This is your chance to save any persistent information. Use the
SharedPreferences object to save the scores like so:
Note that the SharedPreferences.Editor has methods for storing any primitive type. Calling commit()
replaces any of the data that was previously stored in SharedPreferences with the new data.
4. Now we need to restore the game scores from mPrefs when the onCreate() method is being called:
5. Your app will now save the scores indefinitely. However, the user may not want to be reminded that
Android beat him 20 times at tic-tac-toe. So let’s give the user the ability to reset the scores. Add a new
menu item to r es/menu/options_menu.xml labeled “Reset Scores”, and add an image for the menu item.
To keep the menu from getting cluttered, remove the Quit option which really isn’t necessary anyway.
6. When the Reset Scores menu item is selected, set the m HumanWins, mComputerWins, and mTies variables to
0 and re-display the scores.
7. Since we are saving the values of mHumanWins, mComputerWins, and mTies in the preferences, you can safely
remove all the code you previously entered that saved their values with savedInstanceState.
Now run your application and play a few games. Take a look at the scores. Click the Back button on the
emulator and restart your game by clicking on the TicTacToe icon or starting it from Eclipse. The scores should
be the same as they were before you started a new game. Now test your Reset Scores menu item. The scores
should immediately go back to 0.
You can see the values that were stored in your shared preferences by going back to Eclipse and opening the
DDMS perspective. This is done by clicking the DDMS button in the upper-right corner of Eclipse. If you don’t
see this button, select Window > Open Perspective > Other… from Eclipse’s menu and choose DDMS from the
dialog box. The DDMS persepective will now be open, and you will see a DDMS button in the upper-right corner
of Eclipse.
The figure above shows the File Explorer which was used to navigate to
data/data/edu.harding.tictactoe/shared_prefs. Here you will find the ttt_prefs.xml file that was
created by your app. If you would like to see the contents of the file, select the file and press the disk icon which
Extra Challenge
1. The game’s difficulty level is not being saved, so every time the game starts, the difficulty level is reset to
Expert. Fix this by saving the game difficulty level to SharedPreferences. This may be somewhat
challenging because the game’s difficulty level is using an enumerated type, and the
SharedPreferences.Editor doesn’t have a method to save the value of an enumerated type. You should
save an integer which corresponds to the enumerated value and convert that int back into an enumerated
type when you retrieve the preference.
2. If you press Ctrl-F11 immediately after making a move and before the computer has moved, the application
will likely crash. You’ll find the LogCat helpful for determining what is causing the crash. A simple try-catch
block around the line of code causing the exception will stop the app from crashing. However, the game will
still not work correctly when changing the orientation immediately before the computer moves; the game
will indicate it’s still Android’s turn, but the computer will never make a move. This problem has to do with
the H andler executing code on old instances of an A ctivity which has been terminated. You need to fix
this problem by making the computer make a move after the new Activity has restarted if it’s the
computer’s turn to move. This requires adding a few lines of code to the o nCreate() method.
Except as otherwise noted, the content of this document is licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0
Introduction
The PreferenceActivity simplifies the creation of a settings screen for your application. It uses an XML file
(preferences.xml) to list the various application settings. The preferences are saved using the
SharedPreferencesclass, just as they were in the previous tutorial. In some cases, very little to no code needs
to be written to change application settings.
The first thing you need to do is create an XML file that lists the three application settings for our tic-tac-toe
game:
These three are not the only types of preferences, but they are probably
the most popular types of preferences.
<?xmlversion="1.0"encoding="utf-8"?>
<PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="sound"
android:title="Sound"
android:defaultValue="true"
android:summary="Turn the sound on or off"/>
<EditTextPreference
android:key="victory_message"
android:summary=""
android:defaultValue="@string/result_human_wins"
android:title="Victory message"/>
<ListPreference
android:key="difficulty_level"
android:title="Difficulty level"
android:summary=""
android:defaultValue="@string/difficulty_expert"
android:entries="@array/list_difficulty_level"
android:entryValues="@array/list_difficulty_level"/>
</PreferenceScreen>
The PreferenceScreen can have any number of preferences inside it. If there are a large number of
preferences which can be organized by type, it’s a good idea to organize these into PreferenceCategory’s.
Since we only have three preferences, a PreferenceCategory is not necessary.
Note that some of the default values for the preferences are coming strings that we defined earlier in the
strings.xml file (result_human_wins and difficulty_expert). The ListPreference will get its values
from an arrays.xml file we’ll create in the next step.
<string-arrayname="list_difficulty_level">
<item>@string/difficulty_easy</item>
<item>@string/difficulty_harder</item>
<item>@string/difficulty_expert</item>
</string-array>
</resources>
This file is creating a list that will be displayed when selecting the difficulty level, and it uses strings from the
strings.xml file rather than hard-coding text that may need to be changed sometime in the future.
5. Now add a Java class to your project calledSettings which extends the PreferenceActivity class. This
class shouldspecify in the onCreate()methodthat thepreferences.xmlfile will be used to display the list of
preferences:
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
Now we need to display the settings which we’ll do in the next step.
Since we are using the Preferences to set the difficulty level, we don’t need to do this using the application
menu anymore. We’ll replace the Difficulty menu button with one that displays the settings.
1. In res/menu/options_menu.xml, replace the Difficulty menu with a Settings menu option. You will also
need to add an image to res/drawable for representing the settings option.
2. In AndroidTicTacToe.java, remove the final int DIALOG_DIFFICULTY_ID and the code for creating the
difficulty level dialog box in onCreateDialog().
3. Replace the code for handing the Difficulty menu option in onOptionsItemSelected() with code that will
launch the Settings activity. It will use an Intent to start the activity:
case R.id.settings:
startActivityForResult(new Intent(this, Settings.class), 0);
returntrue;
The startActivityForResult() function indicates that we would like to be notified when the Settings
activity is exited (when the user presses the Android device’s Back button). This is important since we’ll
need to change some class-level variables if the sound is turned on or off or if the difficulty level is changed.
4. Now create the onActivityResult()method which will be called when the Settings activity is exited. The
resultCode will be set to RESULT_CANCELED if the user clicks the Back button on their device.
@Override
protectedvoid onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RESULT_CANCELED) {
// Apply potentially new settings
if (difficultyLevel.equals(getResources().getString(R.string.difficulty_easy)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Easy);
elseif (difficultyLevel.equals(getResources().getString(R.string.difficulty_harder)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Harder);
else
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Expert);
}
}
}
The mSoundOn variable from above is not yet defined.It is left to you to declare this class-level boolean
variable and use it to determine if the mHumanMediaPlayer or mComputerMediaPlayer should be played.
5. In order for the Intent to work, you must also modify the app’s AndroidManifest.xml file, adding the
following inside the <application> tags which indicate the Settings activity should be included as part of
this app:
6. Thesound settings and the difficulty level need to be setin AndroidTicTacToe’sonCreate() method like
so:
Note that the code segment above initializes the mPrefs variable differently than was done in the previous
tutorial. This is so the PreferenceActivity and the AndroidTicTacToe activity can both be using the
same settings.
elseif (winner == 2) {
mHumanWins++;
mHumanScoreTextView.setText(Integer.toString(mHumanWins));
String defaultMessage = getResources().getString(R.string.result_human_wins);
mInfoTextView.setText(mPrefs.getString("victory_message", defaultMessage));
}
Run your app andlaunch the Settings screen by selecting the Menu and pressing Settings. Verify that you can
now turn the sound on and off, set the victory message, and set the difficulty level. We’ve gained a lot of
functionality with little code.
You can view the settings using the File Explorer in the DDMS perspective in Eclipse. Look under the
data/data/edu.harding.tictactoe/shared_prefs directory, and you will see a file called
edu.harding.tictactoe_preferences.xml.
There is one thing that would make the settings a little more user friendly… displaying the actual victory
message and difficulty level inside the settings menu. That way the user doesn’t have to actually select the
setting just to see what it is set to. In other words, we want to change the settings on the left to look like the
settings on the right:
1. In the Settings.java file, add the following code to the onCreate() method:
difficultyLevelPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
publicboolean onPreferenceChange(Preference preference, Object newValue) {
difficultyLevelPref.setSummary((CharSequence) newValue);
The code above first sets the difficulty level’s setting’s summary to the currently selected difficulty level.
Then it creates an OnPreferenceChangeListener which will execute when the difficulty level is changed.
This is necessary so we can change the summary when a different level is selected, but it also makes us
manually have to save the preference.
2. You will need to write similar code to retrieve the victory message, display it as the summary text, and add
an OnPreferenceChangeListener to save the message. The code below will get you started:
finalEditTextPreferencevictoryMessagePref = (EditTextPreference)
findPreference("victory_message");
String victoryMessage = prefs.getString("victory_message",
getResources().getString(R.string.result_human_wins));
Run the app again and verify that the settings now display the current victory message and difficulty level.
Extra Challenge
Create a Board color preference that will allow the user to change the board color. An ideal method is to display
the ColorPickerDialog that is provided in the Android API demos:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/ColorPic
kerDialog.html
As shown in the figure below, the user clicks an area around the circle to pick a color and then must click the
circle in the middle to dismiss the dialog. When they return to the game, the board color should match the
selected color.
Except as otherwise noted, the content of this document is licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0