6. Flag Quiz Game App

Objectives

In this chapter you’ll:

• Store String arrays in strings.xml.

• Store a set of images in subfolders of the assets folder.

• Use an AssetManager to get a list of all assets in an app.

• Use random-number generation to vary flag choices.

• Use a Drawable to display a flag image in an ImageView.

• Use a tweened animation to shake the displayed flag when the user specifies an incorrect answer.

• Use a Handler to schedule a future action.

• Use an ArrayList to hold collections of items and a HashMap to hold name–value pairs.

• Override Activity’s onCreateOptionsMenu method to create a Menu and MenuItems that enable the user to configure the app’s options.

• Use Android’s logging mechanism to log error messages.

Outline

6.1 Introduction

6.2 Test-Driving the Flag Quiz Game App

6.3 Technologies Overview

6.4 Building the App’s GUI and Resource Files

6.4.1 main.xml LinearLayout

6.4.2 Creating the Project

6.4.3 Creating and Editing the Resource Files

6.4.4 Adding the Components to the LinearLayout

6.4.5 Creating a Button That Can Be Dynamically Inflated

6.4.6 Creating the Flag Shake Animation

6.5 Building the App

6.6 AndroidManifest.xml

6.7 Wrap-Up

Self-Review Exercises | Answers to Self-Review Exercises | Exercises

6.1. Introduction

The Flag Quiz Game app tests the user’s ability to correctly identify country flags (Fig. 6.1). Initially, the app presents the user with a flag image and three possible answers—one matches the flag and the others are randomly selected, nonduplicated incorrect answers. The app displays the user’s progress throughout the quiz, showing the question number (out of 10) in a TextView above the current flag image.

Image

Fig. 6.1. Flag Quiz Game app.

User Making a Correct Selection

The user chooses the country by touching the corresponding Button. If the choice is correct, the app disables all the answer Buttons and displays the country name in green followed by an exclamation point at the bottom of the screen (Fig. 6.2). After a one-second delay, the app loads the next flag and displays a new set of answer Buttons.

Image

Fig. 6.2. User choosing the correct answer and the correct answer displayed.

User Making an Incorrect Selection

If the user selects incorrectly, the app disables the corresponding country name Button, uses an animation to shake the flag and displays Incorrect! in red at the bottom of the screen (Fig. 6.3). The user keeps choosing countries until the correct one is picked.

Image

Fig. 6.3. Disabled incorrect answer in the Flag Quiz Game app.

Completing the 10 Questions

After the user selects the 10 correct country names, a popup AlertDialog displays over the app and shows the user’s total number of guesses and the percentage of correct answers (Fig. 6.4). When the user touches the dialog’s Reset Quiz Button, a new quiz begins based on the current quiz options.

Image

Fig. 6.4. Results alert after quiz completion.

Customizing the Number of Answers Displayed with Each Flag

The user can customize the quiz by using the app’s menu. When the user touches the device’s menu button, the menu options Select Number of Choices and Select Regions are displayed. When the user touches Select Number of Choices, the app displays an AlertDialog from which the user can select 3, 6 or 9 as the number of answers to display below each flag (Fig. 6.5). When the user touches an option, the game restarts with the specified number of answers for each flag (and the currently enabled world regions).

Image

Fig. 6.5. Menu of the Flag Quiz Game app.

Customizing the Regions from Which Flags Are Selected

When the user touches Select Regions in the app’s menu, the app displays an AlertDialog containing a checkbox for each world region (Fig. 6.6)—five of the major continents and Oceania, which consists of Australia, New Zealand and various South Pacific islands. If a region’s checkbox is checked, flags from that region can be used in the quiz. When the user touches the Reset Quiz Button, the game restarts with flags selected from the current enabled regions.

Image

Fig. 6.6. Choices Dialog of the Flag Quiz Game app.

6.2. Test-Driving the Flag Quiz Game App

Opening and Running the App

Open Eclipse and import the Flag Quiz Game app project. Perform the following steps:

1. Open the Import Dialog. Select File > Import... to open the Import dialog.

2. Import the FlagQuiz Game app’s project. In the Import dialog, expand the General node and select Existing Projects into Workspace, then click Next > to proceed to the Import Projects step. Ensure that Select root directory is selected, then click the Browse... button. In the Browse For Folder dialog, locate the FlagQuizGame folder in the book’s examples folder, select it and click OK. Click Finish to import the project into Eclipse. The project now appears in the Package Explorer window at the left side of the Eclipse window.

3. Launch the FlagQuiz Game app. In Eclipse, right click the FlagQuizGame project in the Package Explorer window, then from the menu that appears select Run As > Android Application.

Configuring the Quiz

Touch the Menu Button (or your device’s menu button) to access the menu so you can view the app’s options. Touch Select Number of Choices to specify the number of answers that should be displayed with each flag (as in Fig. 6.5). By default, three choices are displayed with each flag when the app is first executed. Touch 6 to display six answers with each flag.

Touch Select Regions to display the checkboxes representing the world regions (as in Fig. 6.6). By default, all regions are enabled when the app is first executed, so any of the world’s flags can be selected randomly for the quiz. Touch the checkboxes next to Africa and Oceania to uncheck them—this excludes the countries of those regions from the quiz. Touch Reset Quiz to start a new game with the updated settings.

Completing the Quiz

A new quiz starts with six answer choices and no flags from either Africa or Oceania. Work through the quiz by touching the country that you think matches each flag. If you guess incorrectly, keep guessing until you get the correct answer for that flag. After you’ve successfully matched 10 flags, the quiz is grayed out and an AlertDialog displays the number of guesses you made and your accuracy percentage (as in Fig. 6.4). Touch the Reset Quiz Button to take another quiz.

6.3. Technologies Overview

Using the App’s assets Folder

The app contains one image for each flag.1 These images are loaded into the app only when needed. The images are located in the app’s assets folder—we dragged each region’s folder from our file system onto the assets folder. These folders are located with the book’s examples in the images/FlagQuizGameImages folder. Unlike an app’s drawable folders, which require their image contents to be at the root level in each folder, the assets folder may contain files of any type that can be organized in subfolders—we maintain the flag images for each region in a separate subfolder. Files in the assets folders are accessed via an AssetManager (package android.content.res), which can provide a list of all of the file names in a specified subfolder of assets and can be used to access each asset.

1 We obtained the images from www.free-country-flags.com.

When the app needs to display a quiz question’s flag, we use the AssetManager to open an InputStream (package java.io) to read from the flag image’s file. Next, we use that stream as an argument to class Drawable’s static method createFromStream, which creates a Drawable object. That Drawable (package android.graphics.drawable) is then set as an ImageView’s item to display with ImageView’s setImageDrawable method.

Using a Menu to Provide App Options

The number of answer choices displayed and the regions from which flags can be selected can each be set by the user via the app’s Menu (package android.view). To specify the Menu options, you override Activity’s onCreateOptionsMenu method and add the options to the Menu that the method receives as an argument. When the user selects an item from the Menu, Activity method onOptionsItemSelected is called to respond to the selection. We override this method to display the corresponding options in AlertDialogs.

Using a Handler to Execute a Runnable in the Future

To delay displaying the next flag after a correct guess, we use a Handler (package android.os) object to execute a Runnable after a 1,000-millisecond delay. Handler method postDelayed receives as arguments a Runnable to execute and a delay in milliseconds.

Animating the Flag When an Incorrect Choice Is Touched

When the user makes an incorrect choice, the app shakes the flag by applying an Animation (package android.view.animation) to the ImageView. We use AnimationUtils static method loadAnimation to load the animation from an XML file that specifies the animation’s options. We also specify the number of times the animation should repeat with Animation method setRepeatCount and perform the animation by calling View method startAnimation (with the Animation as an argument) on the ImageView.

Logging Exception Messages with Log.e

When exceptions occur, you can log them for debugging purposes with Android’s built-in logging mechanism, which uses a circular buffer to store the messages for a short time. Android provides class Log (package android.util) with several static methods that represent messages of varying detail. Logged messages can be viewed with the Android logcat tool. These messages are also displayed in the Android DDMS (Dalvik Debug Monitor Server) perspective’s LogCat tab in Eclipse. For more details on logging messages, visit

developer.android.com/reference/android/util/Log.html

Java Data Structures

This app uses various data structures from the java.util package. The app dynamically loads the image file names for the enabled regions and stores them in an ArrayList<String>. We use Collections method shuffle to randomize the order of the image file names in the ArrayList<String> for each new game. We use a second ArrayList<String> to hold the image file names of the 10 countries in the current quiz. We also use a HashMap<String, Boolean> to store the region names and corresponding Boolean values, indicating whether each region is enabled or disabled. We refer to the ArrayList<String> and HashMap<String, Boolean> objects with variables of interface types List<String> and Map<String, Boolean>, respectively—this is a good Java programming practice that enables you to change data structures easily without affecting the rest of your app’s code. In addition, we use interface Set<String> when referring to the keys in the HashMap.

6.4. Building the App’s GUI and Resource Files

In this section, you’ll build the GUI for the Flag Quiz Game app. You’ll create a second XML layout that will be dynamically inflated to create the country-name Buttons that represent each quiz question’s possible answers. You’ll also create an XML representation of the shake animation that’s applied to the flag image when the user guesses incorrectly.

6.4.1. main.xml LinearLayout

In this app, we use main.xml’s default vertical LinearLayout. Figure 6.7 shows the app’s GUI component names. Recall that, for clarity, our naming convention is to use the GUI component’s class name in each component’s Id property in the XML layout and in each variable name in the Java code.

Image

Fig. 6.7. Flag Quiz Game GUI’s components labeled with their Id property values.

6.4.2. Creating the Project

Begin by creating a new Android project named FlagQuizGame. Specify the following values in the New Android Project dialog, then press Finish:

Build Target: Ensure that Android 2.3.3 is checked

Application name: FlagQuizGame

Package name: com.deitel.flagquizgame

Create Activity: FlagQuizGame

Min SDK Version: 8.

6.4.3. Creating and Editing the Resource Files

As in the previous app, create the files colors.xml and dimen.xml to store literal color and dimension values, respectively. To create each file:

1. Right click the project name in the Package Explorer window and select New > Other..., then select Android XML File from the Android node in the New dialog. This displays the New Android XML File dialog.

2. In the File text field, enter the name colors.xml.

3. Under What type of resource would you like to create?, select the Values radio button to place the new file in the project’s res/values folder.

4. Click Finish to create the file.

5. Repeat this process to create the dimen.xml file.

The contents of these two files are shown in Figs. 6.86.9. We use these colors and dimensions in main.xml. You should add these resources to these files in your project.


1   <?xml version="1.0" encoding="UTF-8"?>
2   <resources>
3      <color name="text_color">#000000</color>
4      <color name="background_color">#FFFFCC</color>
5      <color name="correct_answer">#00CC00</color>
6      <color name="incorrect_answer">#FF0000</color>
7   </resources>


Fig. 6.8. Colors defined in colors.xml.


1   <?xml version="1.0" encoding="UTF-8"?>
2   <resources>
3      <dimen name="title_size">25sp</dimen>
4      <dimen name="flag_width">227dp</dimen>
5      <dimen name="flag_height">150dp</dimen>
6      <dimen name="answer_size">40sp</dimen>
7      <dimen name="text_size">20sp</dimen>
8   </resources>


Fig. 6.9. Dimensions defined in dimen.xml.

strings.xml

As in previous apps, we defined String resources in strings.xml (Fig. 6.10). For the first time, we also defined two String arrays in strings.xml. These arrays represent the region names (lines 18–25) and the number of answer Buttons displayed with each question (lines 26–30), respectively. You can enter these directly in the XML using the elements string-array and item as shown in Fig. 6.10.


 1   <?xml version="1.0" encoding="UTF-8"?>
 2   <resources>
 3      <string name="app_name">FlagQuizGame</string>
 4      <string name="choices">Select Number of Choices</string>
 5      <string name="correct">correct</string>
 6      <string name="guess_country">Guess the Country</string>
 7      <string name="guesses">guesses</string>
 8      <string name="incorrect_answer">Incorrect!</string>
 9      <string name="more_regions_title">More Regions Required</string>
10      <string name="more_regions_message">There are not enough countries in
11         the selected regions. Please select more regions.</string>
12      <string name="of">of</string>
13      <string name="ok">OK</string>
14      <string name="question">Question</string>
15      <string name="quiz_title">Ten Question Flag Quiz</string>
16      <string name="regions">Select Regions</string>
17      <string name="reset_quiz">Reset Quiz</string>
18      <string-array name="regionsList">
19         <item>Africa</item>           
20         <item>Asia</item>             
21         <item>Europe</item>           
22         <item>North_America</item>    
23         <item>Oceania</item>          
24         <item>South_America</item>    
25      </string-array>                  
26      <string-array name="guessesList">
27         <item>3</item>                
28         <item>6</item>                
29         <item>9</item>                
30      </string-array>                  
31   </resources>


Fig. 6.10. Strings defined in strings.xml.

You can also use the resource-file editor to create these arrays as follows:

1. Click the Add... button in the editor, then select String Array from the dialog that appears and click OK.

2. Specify the array name in the Name field on the editor window’s right side.

3. Next, right click the array name in the resource list and select Add... from the popup menu, then click OK to add a new Item to the array.

4. Repeat Step 3 for the required number of array elements.

5. Select each Item in the resource list and specify its value in the Value field on the editor window’s right side

6.4.4. Adding the Components to the LinearLayout

Using the techniques you learned in earlier chapters, build the GUI in Fig. 6.7. You’ll start with the basic layout and controls, then customize the controls’ properties to complete the design. Use the resources in strings.xml (Fig. 6.10), colors.xml (Fig. 6.8) and dimen.xml (Fig. 6.9) as necessary. We summarize building this app’s GUI here. In subsequent apps, we’ll focus only on the new GUI features, but still provide the final XML layout so you can see the attributes we set for each component.

Step 1: Configuring the LinearLayout

In the Outline window, select the LinearLayout and set the following properties:

Background: @color/background_color

Gravity: center_horizontal

Id: @+id/linearLayout

Also change the Layout width and Layout height property values from fill_parent (which is deprecated) to match_parent.

Step 2: Adding the Components and Configuring Their Properties

Using Fig. 6.7 as your guide, add the TextViews, ImageView and TableLayout to the app’s linearLayout. As you add these components, set their Id and Text properties. Study the XML elements in the final main.xml file (Fig. 6.11) to see each component’s attribute values. We’ve highlighted important features and the resources we used. Don’t create any Buttons in the TableRows—the Buttons are generated dynamically during the quiz.


 1   <?xml version="1.0" encoding="utf-8"?>
 2
 3   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 4      android:id="@+id/linearLayout" android:orientation="vertical"
 5      android:layout_width="match_parent"
 6      android:layout_height="match_parent"
 7      android:gravity="center_horizontal"
 8      android:background="@color/background_color">
 9
10      <TextView android:id="@+id/titleTextView"
11         android:layout_width="match_parent"
12         android:layout_height="wrap_content"
13         android:text="@string/quiz_title" android:layout_marginBottom="10dp"
14         android:textSize="@dimen/title_size"
15         android:textColor="@color/text_color" android:gravity="center">
16      </TextView>
17
18      <TextView android:id="@+id/questionNumberTextView"
19         android:layout_width="match_parent"
20         android:layout_height="wrap_content"
21         android:layout_marginBottom="10dp" android:layout_marginTop="10dp"
22         android:textColor="@color/text_color"
23         android:textSize="@dimen/text_size" android:layout_gravity="center"
24         android:gravity="center"></TextView>
25
26      <ImageView android:id="@+id/flagImageView"
27         android:adjustViewBounds="false"
28         android:layout_width="@dimen/flag_width"
29         android:layout_height="@dimen/flag_height" ></ImageView>
30
31      <TextView android:id="@+id/guessCountryTextView"
32         android:layout_width="wrap_content"
33         android:layout_height="wrap_content"
34         android:layout_marginBottom="10dp" android:layout_marginTop="10dp"
35         android:text="@string/guess_country"
36         android:textColor="@color/text_color"
37         android:textSize="@dimen/text_size"></TextView>
38
39      <TableLayout android:id="@+id/buttonTableLayout"
40         android:layout_width="match_parent"
41         android:layout_height="wrap_content"
42         android:layout_weight="1" android:stretchColumns="0,1,2">
43         <TableRow android:id="@+id/tableRow0"
44            android:layout_width="match_parent"
45            android:layout_height="wrap_content"
46            android:orientation="horizontal"></TableRow>
47         <TableRow android:id="@+id/tableRow1"
48            android:layout_width="match_parent"
49            android:layout_height="wrap_content"
50            android:orientation="horizontal"></TableRow>
51         <TableRow android:id="@+id/tableRow2"
52            android:layout_width="match_parent"
53            android:layout_height="wrap_content"
54            android:orientation="horizontal"></TableRow>
55      </TableLayout>
56
57      <TextView android:id="@+id/answerTextView"
58         android:layout_width="match_parent"
59         android:layout_height="wrap_content"
60         android:textSize="@dimen/answer_size"
61         android:layout_gravity="center" android:textStyle="bold"
62         android:gravity="center"></TextView>
63   </LinearLayout>


Fig. 6.11. FlagQuizGame app’s XML layout (main.xml).

Notes on main.xml

Line 27 introduces the ImageView attribute android:adjustViewBounds, which specifies whether or not the ImageView maintains the aspect ratio of its Drawable. In this case we set it to false so we can size the flag images.

You’ll notice in line 42 that we set buttonTableLayout’s android:layout_weight attribute to 1. This value makes buttonTableLayout more important than the other components when the main linearLayout is resized based on the available space. Because buttonTableLayout is the only component that specifies an android:layout_weight, it stretches vertically to occupy all remaining vertical space that’s not occupied by the other components. Also, the buttonTableLayout’s android:stretchColumns attribute is set to 0,1,2 to ensure that all three columns in a given TableRow stretch to fill the available horizontal space.

6.4.5. Creating a Button That Can Be Dynamically Inflated

Next, you’ll define an XML representation of a Button. The app inflates this XML file to create each answer Button. In Section 6.5, you’ll configure these Buttons and attach them to the appropriate TableRow. To create another layout XML layout file:

1. Right click the layout folder and select New > Other... to display the New dialog.

2. In the Android node, select Android XML File and click Next > to display the New Android XML File dialog.

3. In the File text field, enter the name guess_button.xml.

4. Under What type of resource would you like to create?, select the Layout radio button. This places the new file guess_button.xml into the project’s res/layout folder.

5. At the bottom of the dialog, you can select the root element for the new layout. Choose Button.

6. Click Finish to create the file. The file opens immediately in XML view.

7. Configure the Button’s attributes as shown in Fig. 6.12.


1   <?xml version="1.0" encoding="UTF-8"?>
2   <Button xmlns:android="http://schemas.android.com/apk/res/android"
3      android:id="@+id/newGuessButton" android:layout_weight="1"
4      android:layout_width="wrap_content"
5      android:layout_height="wrap_content"></Button>


Fig. 6.12. The newGuessButton that will be dynamically inflated (guess_button.xml).

6.4.6. Creating the Flag Shake Animation

The XML in Fig. 6.13 defines the flag shake animation that we use when the user makes an incorrect guess. We’ll show how this XML-defined animation is used by the app in Section 6.5.


 1   <?xml version="1.0" encoding="utf-8"?>
 2
 3   <set xmlns:android="http://schemas.android.com/apk/res/android"
 4      android:interpolator="@android:anim/decelerate_interpolator">
 5
 6      <translate android:fromXDelta="0" android:toXDelta="-5%p"
 7         android:duration="100"/>
 8
 9      <translate android:fromXDelta="-5%p" android:toXDelta="5%p"
10         android:duration="100" android:startOffset="100"/>
11
12      <translate android:fromXDelta="5%p" android:toXDelta="-5%p"
13         android:duration="100" android:startOffset="200"/>
14   </set>


Fig. 6.13. Shake animation (incorrect_shake.xml) that’s applied to the flag when the user guesses incorrectly.

To create this animation file:

1. Right click the layout folder and select New > Other... to display the New dialog.

2. In the Android node, select Android XML File and click Next > to display the New Android XML File dialog.

3. In the File text field, enter the name incorrect_shake.xml.

4. Under What type of resource would you like to create?, select the Animation radio button. This places the new file incorrect_shake.xml into the project’s res/anim folder.

5. At the bottom of the dialog, you can select set as the animation’s root element.

6. Click Finish to create the file. The file opens immediately in XML view.

7. Configure the animation as shown in Fig. 6.13.

In this example, we use View animations to create a shake effect that consists of three animations in an animation set (lines 3–14)—a collection of animations which make up a larger animation. Animation sets may contain any combination of tweened animationsalpha (transparency), scale (resize), translate (move) and rotate. Our shake animation consists of a series of three translate animations. A translate animation moves a View within its parent. As of version 3.0, Android now supports property animations in which you can animate any property of any object. We use property animations in our SpotOn Game app in Chapter 8.

The first translate animation (lines 6–7) moves a View from a starting location to an ending position over a specified period of time. The android:fromXDelta attribute is the View’s offset when the animation starts and the android:toXDelta attribute is the View’s offset when the animation ends. These attributes can have

• absolute values (in pixels)

• a percentage of the animated View’s size

• a percentage of the animated View’s parent’s size

For the android:fromXDelta attribute, we specified an absolute value of 0. For the android:toXDelta attribute, we specified the value -5%p, which indicates that the View should move to the left (due to the minus sign) by 5% of the parent’s width (indicated by the p). If we wanted to move by 5% of the View’s width, we would leave out the p. The android:duration attribute specifies how long the animation lasts in milliseconds. So the animation in lines 6–7 will move the View to the left by 5% of its parent’s width in 100 milliseconds.

The second animation (lines 9–10) continues from where the first finished, moving the View from the -5%p offset to a %5p offset in 100 milliseconds. By default, animations in an animation set are applied in parallel, but you can use the android:startOffset attribute to specify the number of milliseconds into the future at which an animation should begin. This can be used to sequence the animations in a set. In this case, the second animation starts 100 milliseconds after the first. The third animation (lines 12–13) is the same as the second but in the reverse direction, and it starts 200 milliseconds after the first animation.

6.5. Building the App

Figures 6.146.22 implement the Flag Quiz Game app in the single class FlagQuizGame, which extends Activity.


 1   // FlagQuizGame.java
 2   // Main Activity for the Flag Quiz Game App
 3   package com.deitel.flagquizgame;
 4
 5   import java.io.IOException;  
 6   import java.io.InputStream;  
 7   import java.util.ArrayList;  
 8   import java.util.Collections;
 9   import java.util.HashMap;    
10   import java.util.List;       
11   import java.util.Map;        
12   import java.util.Random;     
13   import java.util.Set;        
14
15   import android.app.Activity;
16   import android.app.AlertDialog;
17   import android.content.Context;
18   import android.content.DialogInterface;
19   import android.content.res.AssetManager;  
20   import android.graphics.drawable.Drawable;
21   import android.os.Bundle;
22   import android.os.Handler;
23   import android.util.Log;  
24   import android.view.LayoutInflater;
25   import android.view.Menu;    
26   import android.view.MenuItem;
27   import android.view.View;
28   import android.view.View.OnClickListener;
29   import android.view.animation.Animation;     
30   import android.view.animation.AnimationUtils;
31   import android.widget.Button;
32   import android.widget.ImageView;
33   import android.widget.TableLayout;
34   import android.widget.TableRow;
35   import android.widget.TextView;
36


Fig. 6.14. FlagQuizGames’s package and import statements.

The package and import Statements

Figure 6.14 shows the package statement and import statements in FlagQuizGame.java. The package statement in line 3 indicates that the class in this file is part of the package com.deitel.flagquizgame—this line was inserted when you created the project. Lines 5–35 import the various Java and Android classes and interfaces the app uses. We discussed those that are new in this app in Section 6.3.

Instance Variables

Figure 6.15 lists class FlagQuizGame’s variables. Line 40 declares the static final String TAG, which is used when we log error messages using class Log (Fig. 6.17) to distinguish this Activity’s error messages from others that are being written to the device’s log.


37   public class FlagQuizGame extends Activity
38   {
39      // String used when logging error messages
40      private static final String TAG = "FlagQuizGame Activity";
41
42      private List<String> fileNameList; // flag file names                
43      private List<String> quizCountriesList; // names of countries in quiz
44      private Map<String, Boolean> regionsMap; // which regions are enabled
45      private String correctAnswer; // correct country for the current flag
46      private int totalGuesses; // number of guesses made
47      private int correctAnswers; // number of correct guesses
48      private int guessRows; // number of rows displaying choices
49      private Random random; // random number generator
50      private Handler handler; // used to delay loading next flag       
51      private Animation shakeAnimation; // animation for incorrect guess
52
53      private TextView answerTextView; // displays Correct! or Incorrect!
54      private TextView questionNumberTextView; // shows current question #
55      private ImageView flagImageView; // displays a flag
56      private TableLayout buttonTableLayout; // table of answer Buttons
57


Fig. 6.15. FlagQuizGame class’s instance variables.

The List<String> object fileNameList holds the flag image file names for the currently enabled geographic regions. The List<String> object quizCountriesList holds the 10 flag file names for the countries in the quiz. The Map<String, Boolean> object regionsMap stores the geographic regions that are enabled.

The String correctAnswer holds the flag file name for the current flag’s correct answer. The int totalGuesses stores the total number of correct and incorrect guesses the player has made so far. The int correctAnswers is the number of correct guesses so far; this will eventually be 10 if the user completes the quiz. The int guessRows is the number of three-Button rows displaying the flag answer choices.

The Random object random is the pseudorandom-number generator that we use to randomly pick the flags that will be included in the quiz and to randomly select the row and column where the correct answer’s Button will be placed. We use the Handler object handler to delay by one second the loading of the next flag to be tested.

The Animation shakeAnimation holds the dynamically inflated shake animation that’s applied to the flag image when an incorrect guess is made. Lines 53–56 contain variables that we use to manipulate various GUI components programatically.

Overriding Method OnCreate of Class Activity

Method onCreate (Fig. 6.16) inflates the GUI and initializes the Activity’s instance variables. As in prior apps, we first call the superclass’s onCreate method (line 62), then inflate the Activity’s GUI (line 63).


58      // called when the activity is first created
59      @Override
60      public void onCreate(Bundle savedInstanceState)
61      {
62         super.onCreate(savedInstanceState); // call the superclass's method
63         setContentView(R.layout.main); // inflate the GUI
64
65         fileNameList = new ArrayList<String>(); // list of image file names
66         quizCountriesList = new ArrayList<String>(); // flags in this quiz
67         regionsMap = new HashMap<String, Boolean>(); // HashMap of regions
68         guessRows = 1; // default to one row of choices
69         random = new Random(); // initialize the random number generator
70         handler = new Handler(); // used to perform delayed operations
71
72         // load the shake animation that's used for incorrect answers 
73         shakeAnimation =                                              
74            AnimationUtils.loadAnimation(this, R.anim.incorrect_shake);
75         shakeAnimation.setRepeatCount(3); // animation repeats 3 times
76
77         // get array of world regions from strings.xml        
78         String[] regionNames =                                
79            getResources().getStringArray(R.array.regionsList);
80
81         // by default, countries are chosen from all regions
82         for (String region : regionNames)
83            regionsMap.put(region, true);
84
85         // get references to GUI components
86         questionNumberTextView =
87            (TextView) findViewById(R.id.questionNumberTextView);
88         flagImageView = (ImageView) findViewById(R.id.flagImageView);
89         buttonTableLayout =
90            (TableLayout) findViewById(R.id.buttonTableLayout);
91         answerTextView = (TextView) findViewById(R.id.answerTextView);
92
93         // set questionNumberTextView's text
94         questionNumberTextView.setText(
95            getResources().getString(R.string.question) + " 1 " +
96            getResources().getString(R.string.of) + " 10");
97
98         resetQuiz(); // start a new quiz
99      } // end method onCreate
100


Fig. 6.16. Overriding method onCreate of class Activity.

Lines 65–66 create ArrayList<String> objects that will store the flag image file names for the currently enabled geographical regions and the 10 countries in the current quiz, respectively. Line 67 creates the HashMap<String, Boolean> that stores whether each geographical region is enabled.

We set guessRows to 1 so that the game initiallys displays only one row of Buttons containing three possible answers. The user has the option to make the game more challenging by displaying two rows (with six possible answers) or three rows (with nine possible answers).

Line 69 creates the Random object random that we use to randomly pick the flags that will be included in the quiz and to randomly select the row and column where the correct answer’s Button will be placed. Line 70 creates the Handler object handler, which we’ll use to delay by one second the appearance of the next flag after the user correctly guesses the current flag.

Lines 73–74 dynamically load the shake animation that will be applied to the flag when an incorrect guess is made. AnimationUtils static method loadAnimation loads the animation from the XML file represented by the constant R.anim.incorrect_shake. The first argument indicates the Context (this FlagQuizGame instance) containing the resources that will be animated. Line 75 specifies the number of times the animation should repeat with Animation method setRepeatCount.

Lines 78–79 dynamically load the contents of the String array regionNames. Method getResources (inherited indirectly from class ContextWrapper) returns a Resources object (package android.content.res) that can be used to load the Activity’s resources. We then call that object’s getStringArray method to load the array associated with the resource constant R.array.regionsList from the file strings.xml.

Lines 82–83 use method put to add each of the six regions to the regions HashMap. Each region is set initially to true (i.e., enabled). The user can enable and disable the regions as desired via the app’s options menu (Figs. 6.206.21).

Lines 86–91 get references to various GUI components that we’ll programmatically manipulate. Lines 94–96 set the text in questionNumberTextView. Here, we could have used String formatting to create questionNumberTextView’s text. In Section 7.4.3, we demonstrate how to create String resources for format Strings. Line 98 calls the FlagQuizGame class’s resetQuiz method to set up the next quiz.

resetQuiz Method of Class FlagQuizGame (Our App)

Method resetQuiz (Fig. 6.17) sets up and starts the next quiz. Recall that the images for the game are stored in the app’s assets folder. To access this folder’s contents, the method gets the app’s AssetManager (line 106) by calling method getAssets (inherited indirectly from class ContextWrapper). Next, line 107 clears the fileNameList to prepare to load image file names for only the enabled geographical regions. We use HashMap method keySet (line 111) to form a set of the six region names from regionsMap and assign it to the Set<String> object regions. Then we iterate through all the regions (lines 114–124). For each region we use the AssetManager’s list method (line 119) to get an array of all the flag image file names, which we store in the String array paths. Lines 121–122 remove the .png extension from each flag image file name and place the names in the fileNameList.


101     // set up and start the next quiz
102     private void resetQuiz()
103     {
104        // use the AssetManager to get the image flag
105        // file names for only the enabled regions
106        AssetManager assets = getAssets(); // get the app's AssetManager
107        fileNameList.clear(); // empty the list
108
109        try
110        {
111           Set<String> regions = regionsMap.keySet(); // get Set of regions
112
113           // loop through each region
114           for (String region : regions)
115           {
116              if (regionsMap.get(region)) // if region is enabled
117              {
118                 // get a list of all flag image files in this region
119                 String[] paths = assets.list(region);
120
121                 for (String path : paths)
122                    fileNameList.add(path.replace(".png", ""));
123              } // end if
124           } // end for
125        } // end try
126        catch (IOException e)
127        {
128           Log.e(TAG, "Error loading image file names", e);
129        } // end catch
130
131        correctAnswers = 0; // reset the number of correct answers made
132        totalGuesses = 0; // reset the total number of guesses the user made
133        quizCountriesList.clear(); // clear prior list of quiz countries
134
135        // add 10 random file names to the quizCountriesList
136        int flagCounter = 1;
137        int numberOfFlags = fileNameList.size(); // get number of flags
138
139        while (flagCounter <= 10)
140        {
141           int randomIndex = random.nextInt(numberOfFlags); // random index
142
143           // get the random file name
144           String fileName = fileNameList.get(randomIndex);
145
146           // if the region is enabled and it hasn't already been chosen
147           if (!quizCountriesList.contains(fileName))
148           {
149              quizCountriesList.add(fileName); // add the file to the list
150              ++flagCounter;
151           } // end if
152        } // end while
153
154        loadNextFlag(); // start the quiz by loading the first flag
155     } // end method resetQuiz
156


Fig. 6.17. resetQuiz method of class FlagQuizGame.

Next, lines 131–133 reset the counters for the number of correct guesses the user has made (correctAnswers) and the total number of guesses the user has made (totalGuesses) to 0 and clear the quizCountriesList.

Lines 136–152 add 10 randomly selected file names to the quizCountriesList. We get the total number of flags, then randomly generate the index in the range 0 to one less than the number of flags. We use this index to select one image file name from fileNamesList. If the quizCountriesList does not already contain that file name, we add it to quizCountriesList and increment the flagCounter. We repeat this process until 10 unique file names have been selected. Then line 154 calls loadNextFlag (Fig. 6.18) to load the quiz’s first flag.


157     // after the user guesses a correct flag, load the next flag
158     private void loadNextFlag()
159     {
160        // get file name of the next flag and remove it from the list
161        String nextImageName = quizCountriesList.remove(0);
162        correctAnswer = nextImageName; // update the correct answer
163
164        answerTextView.setText(""); // clear answerTextView
165
166        // display the number of the current question in the quiz
167        questionNumberTextView.setText(
168           getResources().getString(R.string.question) + " " +
169           (correctAnswers + 1) + " " +
170           getResources().getString(R.string.of) + " 10");
171
172        // extract the region from the next image's name
173        String region =
174           nextImageName.substring(0, nextImageName.indexOf('-'));
175
176        // use AssetManager to load next image from assets folder
177        AssetManager assets = getAssets(); // get app's AssetManager
178        InputStream stream; // used to read in flag images          
179
180        try
181        {
182           // get an InputStream to the asset representing the next flag
183           stream = assets.open(region + "/" + nextImageName + ".png");
184
185           // load the asset as a Drawable and display on the flagImageView
186           Drawable flag = Drawable.createFromStream(stream, nextImageName);
187           flagImageView.setImageDrawable(flag);                            
188        } // end try
189        catch (IOException e)
190        {
191           Log.e(TAG, "Error loading " + nextImageName, e);
192        } // end catch
193
194        // clear prior answer Buttons from TableRows
195        for (int row = 0; row < buttonTableLayout.getChildCount(); ++row)
196           ((TableRow) buttonTableLayout.getChildAt(row)).removeAllViews();
197
198        Collections.shuffle(fileNameList); // shuffle file names
199
200        // put the correct answer at the end of fileNameList
201        int correct = fileNameList.indexOf(correctAnswer);
202        fileNameList.add(fileNameList.remove(correct));
203
204        // get a reference to the LayoutInflater service
205        LayoutInflater inflater = (LayoutInflater) getSystemService(
206           Context.LAYOUT_INFLATER_SERVICE);
207
208        // add 3, 6, or 9 answer Buttons based on the value of guessRows
209        for (int row = 0; row < guessRows; row++)
210        {
211           TableRow currentTableRow = getTableRow(row);
212
213           // place Buttons in currentTableRow
214           for (int column = 0; column < 3; column++)
215           {
216              // inflate guess_button.xml to create new Button
217              Button newGuessButton =
218                 (Button) inflater.inflate(R.layout.guess_button, null);
219
220              // get country name and set it as newGuessButton's text
221              String fileName = fileNameList.get((row * 3) + column);
222              newGuessButton.setText(getCountryName(fileName));
223
224              // register answerButtonListener to respond to button clicks
225              newGuessButton.setOnClickListener(guessButtonListener);
226              currentTableRow.addView(newGuessButton);
227           } // end for
228        } // end for
229
230        // randomly replace one Button with the correct answer
231        int row = random.nextInt(guessRows); // pick random row
232        int column = random.nextInt(3); // pick random column
233        TableRow randomTableRow = getTableRow(row); // get the TableRow
234        String countryName = getCountryName(correctAnswer);
235        ((Button)randomTableRow.getChildAt(column)).setText(countryName);
236     } // end method loadNextFlag
237
238     // returns the specified TableRow
239     private TableRow getTableRow(int row)
240     {
241        return (TableRow) buttonTableLayout.getChildAt(row);
242     } // end method getTableRow
243
244     // parses the country flag file name and returns the country name
245     private String getCountryName(String name)
246     {
247        return name.substring(name.indexOf('-') + 1).replace('_', ' ');
248     } // end method getCountryName
249


Fig. 6.18. loadNextFlag method of FlagQuizGame.

loadNextFlag, getTableRow and getCountryName Methods of Class FlagQuizGame

Method loadNextFlag (Fig. 6.18) loads and displays the next flag and the corresponding set of answer Buttons. The image file names in quizCountriesList have the format

regionName-countryName

without the .png extension. If a regionName or countryName contains multiple words, they’re separated by underscores (_).

Line 161 removes the first name from quizCountriesList and stores it in nextImageName. We also save this in correctAnswer so it can be used later to determine whether the user made a correct guess. Next, we clear the answerTextView and display the current question number in the questionNumerTextView (lines 164–170)—again, here we could have used a formatted String resource as we’ll show in Chapter 7.

Lines 173–174 extract from nextImageName the region to be used as the assets subfolder name from which we’ll load the image. Next we get the AssetManager, then use it in the try statement to open an InputStream for reading from the flag image’s file. We use that stream as an argument to Drawable’s static method createFromStream, which creates a Drawable object. That Drawable is set as flagImageView’s item to display with its setImageDrawable method. If an exception occurs in the try block (lines 180–188), we log it for debugging purposes with Android’s built-in logging mechanism, which provides static methods that provide varying detail in the log messages. Log static method e is used to log errors and is the least verbose in terms of the generated error message. If you require more detail in your log messages, see the complete list of Log methods at

developer.android.com/reference/android/util/Log.html

Lines 195–196 remove all previous answer Buttons from the buttonTableLayout’s three TableRows. Next, line 198 shuffles the fileNameList, and lines 201–202 locate the correctAnswer and move it to the end of the fileNameList—later we’ll insert this answer randomly into the answer Buttons.

Lines 205–206 get a LayoutInflater for inflating the answer Button objects from the layout file guess_button.xml. Lines 209–228 iterate through the rows and columns of the buttonTableLayout (for the current number of guessRows). For each new Button:

• lines 217–218 inflate the Button from guess_button.xml

• line 221 gets the flag file name

• line 222 sets Button’s text with the country name

• line 225 sets the new Button’s OnClickListener, and

• line 226 adds the new Button to the appropriate TableRow.

Lines 231–235 pick a random row (based on the current number of guessRows) and column in the buttonTableLayout, then set the text of the Button in that row and column to the correct answer.

Lines 211 and 233 in method loadNextFlag use utility method getTableRow (lines 239–242) to obtain the TableRow at a specific index in the buttonTableLayout. Lines 222 and 234 use utility method getCountryName (lines 245–248) to parse the country name from the image file name.

submitGuess and disableButtons Methods of Class FlagQuizGame

Method submitGuess (Fig. 6.19) is called when the user clicks a country Button to select an answer. The method receives the clicked Button as parameter guessButton. We get the Button’s text (line 253) and the parsed country name (line 254), then increment totalGuesses.


250     // called when the user selects an answer
251     private void submitGuess(Button guessButton)
252     {
253        String guess = guessButton.getText().toString();
254        String answer = getCountryName(correctAnswer);
255        ++totalGuesses; // increment the number of guesses the user has made
256
257        // if the guess is correct
258        if (guess.equals(answer))
259        {
260           ++correctAnswers; // increment the number of correct answers
261
262           // display "Correct!" in green text
263           answerTextView.setText(answer + "!");
264           answerTextView.setTextColor(
265              getResources().getColor(R.color.correct_answer));
266
267           disableButtons(); // disable all answer Buttons
268
269           // if the user has correctly identified 10 flags
270           if (correctAnswers == 10)
271           {
272              // create a new AlertDialog Builder
273              AlertDialog.Builder builder = new AlertDialog.Builder(this);
274
275              builder.setTitle(R.string.reset_quiz); // title bar string
276
277              // set the AlertDialog's message to display game results
278              builder.setMessage(String.format("%d %s, %.02f%% %s",
279                 totalGuesses, getResources().getString(R.string.guesses),
280                 (1000 / (double) totalGuesses),
281                 getResources().getString(R.string.correct)));
282
283              builder.setCancelable(false);
284
285              // add "Reset Quiz" Button
286              builder.setPositiveButton(R.string.reset_quiz,
287                 new DialogInterface.OnClickListener()
288                 {
289                    public void onClick(DialogInterface dialog, int id)
290                    {
291                       resetQuiz();
292                    } // end method onClick
293                 } // end anonymous inner class
294              ); // end call to setPositiveButton
295
296           // create AlertDialog from the Builder
297           AlertDialog resetDialog = builder.create();
298           resetDialog.show(); // display the Dialog
299        } // end if
300        else // answer is correct but quiz is not over
301        {
302           // load the next flag after a 1-second delay        
303           handler.postDelayed(                                
304              new Runnable()                                   
305              {                                                
306                 @Override                                     
307                 public void run()                             
308                 {                                             
309                    loadNextFlag();                            
310                 }                                             
311              }, 1000); // 1000 milliseconds for 1-second delay
312        } // end else
313     } // end if
314     else // guess was incorrect
315     {
316           // play the animation
317           flagImageView.startAnimation(shakeAnimation);
318
319           // display "Incorrect!" in red
320           answerTextView.setText(R.string.incorrect_answer);
321           answerTextView.setTextColor(
322              getResources().getColor(R.color.incorrect_answer));
323           guessButton.setEnabled(false); // disable the incorrect answer
324        } // end else
325     } // end method submitGuess
326
327     // utility method that disables all answer Buttons
328     private void disableButtons()
329     {
330        for (int row = 0; row < buttonTableLayout.getChildCount(); ++row)
331        {
332           TableRow tableRow = (TableRow) buttonTableLayout.getChildAt(row);
333           for (int i = 0; i < tableRow.getChildCount(); ++i)
334              tableRow.getChildAt(i).setEnabled(false);
335        } // end outer for
336     } // end method disableButtons
337


Fig. 6.19. submitGuess method of FlagQuizGame.

If the guess is correct (line 258), we increment correctAnswers. Next, we set the answerTextView’s text to the country name and change its color to the color represented by the constant R.color.correct_answer, and we call our utility method disableButtons (defined in lines 328–336) to iterate through the buttonTableLayout’s rows and columns and disable all the answer Buttons.

If correctAnswers is 10 (line 270), the quiz is over. Lines 273–299 create a new AlertDialog.Builder, use it to configure the dialog that shows the quiz results, create the AlertDialog and show it on the screen. When the user touches the dialog’s Reset Quiz Button, method resetQuiz is called to start a new game.

If correctAnswers is less than 10, then lines 303–311 call the postDelayed method of Handler object handler. The first argument defines an anonymous inner class that implements the Runnable interface—this represents the task to perform (loadNextFlag) some number of milliseconds into the future. The second argument is the delay in milliseconds (1000).

If the guess is incorrect, line 317 invokes flagImageView’s startAnimation method to play the shakeAnimation that was loaded in method onCreate. We also set the text on answerTextView to display "Incorrect!" in red (lines 320–322), then call the guessButton’s setEnabled method with false (line 323) to disable the Button that corresponds to the incorrect answer.

Overriding Method onCreateOptionsMenu of Class Activity

We override Activity method OnCreateOptionsMenu (Fig. 6.20) to initialize Activity’s standard options menu. The system passes in the Menu object where the options will appear. The app has its own built-in options menu from which the user can select one of two menus by touching either Select Number of Choices or Select Regions. The Select Number of Choices option enables the user to specify whether 3, 6 or 9 flags should be shown for each quiz. The Select Regions option enables the user to enable and disable the geographical regions from which the flags can be selected for a quiz.


338     // create constants for each menu id
339     private final int CHOICES_MENU_ID = Menu.FIRST;
340     private final int REGIONS_MENU_ID = Menu.FIRST + 1;
341
342     // called when the user accesses the options menu
343     @Override
344     public boolean onCreateOptionsMenu(Menu menu)
345     {
346        super.onCreateOptionsMenu(menu);
347
348        // add two options to the menu - "Choices" and "Regions"
349        menu.add(Menu.NONE, CHOICES_MENU_ID, Menu.NONE, R.string.choices);
350        menu.add(Menu.NONE, REGIONS_MENU_ID, Menu.NONE, R.string.regions);
351
352        return true; // display the menu
353     } // end method onCreateOptionsMenu
354


Fig. 6.20. Overriding method onCreateOptionsMenu of class Activity.

Lines 349–340 create constants for two menu IDs. The constant Menu.FIRST represents the option that will appear first in the Menu. Each option should have a unique ID. Method onCreateOptionsMenu first calls call super’s onCreateOptionsMenu. Then we call Menu’s add method to add MenuItems to the Menu (lines 333–334). The first argument represents the MenuItem’s group ID, which is used to group MenuItems that share state (such as whether they’re currently enabled or visible on the screen). This argument should be Menu.NONE if the MenuItem does not need to be part of a group. The second argument is the MenuItem’s unique item ID. The third argument is the order in which the MenuItem should appear—use Menu.NONE if the order of your MenuItems does not matter. The last argument is the resource identifier for the String that will be displayed. We return true to display the menu (line 352).

Overriding Method onOptionsItemSelected of class Activity

Method onOptionsItemSelected (Fig. 6.21) is called when the user selects an item in the app’s options menu and receives the selected MenuItem (item). A switch statement distinguishes between the two cases. The controlling expression of the switch invokes item’s getItemId method to return this menu item’s unique identifier (line 360) so we can determine which MenuItem was selected.


355     // called when the user selects an option from the menu
356     @Override
357     public boolean onOptionsItemSelected(MenuItem item)
358     {
359        // switch the menu id of the user-selected option
360        switch (item.getItemId())
361        {
362           case CHOICES_MENU_ID:
363              // create a list of the possible numbers of answer choices
364              final String[] possibleChoices =
365                 getResources().getStringArray(R.array.guessesList);
366
367              // create a new AlertDialog Builder and set its title
368              AlertDialog.Builder choicesBuilder =
369                 new AlertDialog.Builder(this);
370              choicesBuilder.setTitle(R.string.choices);
371
372              // add possibleChoices items to the Dialog and set the     
373              // behavior when one of the items is clicked               
374              choicesBuilder.setItems(R.array.guessesList,               
375                 new DialogInterface.OnClickListener()                   
376                 {                                                       
377                    public void onClick(DialogInterface dialog, int item)
378                    {                                                    
379                       // update guessRows to match the user's choice    
380                       guessRows = Integer.parseInt(                     
381                          possibleChoices[item].toString()) / 3;         
382                       resetQuiz(); // reset the quiz                    
383                    } // end method onClick                              
384                 } // end anonymous inner class                          
385              ); // end call to setItems                                 
386
387              // create an AlertDialog from the Builder
388              AlertDialog choicesDialog = choicesBuilder.create();
389              choicesDialog.show(); // show the Dialog
390              return true;
391
392           case REGIONS_MENU_ID:
393              // get array of world regions
394              final String[] regionNames =
395                 regionsMap.keySet().toArray(new String[regionsMap.size()]);
396
397              // boolean array representing whether each region is enabled
398              boolean[] regionsEnabled = new boolean[regionsMap.size()];
399              for (int i = 0; i < regionsEnabled.length; ++i)
400                 regionsEnabled[i] = regionsMap.get(regionNames[i]);
401
402              // create an AlertDialog Builder and set the dialog's title
403              AlertDialog.Builder regionsBuilder =
404                 new AlertDialog.Builder(this);
405              regionsBuilder.setTitle(R.string.regions);
406
407              // replace _ with space in region names for display purposes
408              String[] displayNames = new String[regionNames.length];
409              for (int i = 0; i < regionNames.length; ++i)
410                 displayNames[i] = regionNames[i].replace('_', ' ');
411
412                 // add displayNames to the Dialog and set the behavior      
413                 // when one of the items is clicked                         
414                 regionsBuilder.setMultiChoiceItems(                         
415                    displayNames, regionsEnabled,                            
416                    new DialogInterface.OnMultiChoiceClickListener()         
417                    {                                                        
418                       @Override                                             
419                       public void onClick(DialogInterface dialog, int which,
420                          boolean isChecked)                                 
421                       {                                                     
422                          // include or exclude the clicked region           
423                          // depending on whether or not it's checked        
424                          regionsMap.put(                                    
425                             regionNames[which].toString(), isChecked);      
426                       } // end method onClick                               
427                    } // end anonymous inner class                           
428                 ); // end call to setMultiChoiceItems                       
429
430                 // resets quiz when user presses the "Reset Quiz" Button
431                 regionsBuilder.setPositiveButton(R.string.reset_quiz,
432                    new DialogInterface.OnClickListener()
433                    {
434                       @Override
435                       public void onClick(DialogInterface dialog, int button)
436                       {
437                          resetQuiz(); // reset the quiz
438                       } // end method onClick
439                    } // end anonymous inner class
440                 ); // end call to method setPositiveButton
441
442                 // create a dialog from the Builder
443                 AlertDialog regionsDialog = regionsBuilder.create();
444                 regionsDialog.show(); // display the Dialog
445                 return true;
446           } // end switch
447
448           return super.onOptionsItemSelected(item);
449        } // end method onOptionsItemSelected
450


Fig. 6.21. Overriding method onOptionsItemSelected of class Activity.

If the user touched Select Number of Choices the case in lines 362–390 executes. Lines 364–365 obtain the String array guessesList from the app’s resources and assign it to variable possibleChoices. Next, we create a new AlertDialog.Builder and set the dialog’s title (lines 368–370).

Each of the AlertDialogs we’ve created previously has displayed a simple text message and one or two Buttons. In this case, we’d like to display the possibleChoice’s items in the Dialog and specify what to do when the user touches one of the items. To do this, we call AlertDialog.Builder method setItems (lines 374–385). The first argument is an array of Strings or a resource constant representing an array of Strings—these represent a set of mutually exclusive options. The second argument is the DialogInterface.OnClickListener that responds to the user touching one of the items. The listener’s onClick method receives as its second argument the zero-based index of the item the user touched. We use that index to select the appropriate element from possibleChoices, then convert that String to an int and divide it by 3 to determine the number of guessRows. Then, we call resetQuiz to start a new quiz with the specified number of answer Buttons. Lines 388–389 create and display the dialog.

If the user touched Select Regions, the case in lines 392–445 executes to display an AlertDialog containing a list of region names in which multiple items can be enabled. First, we assign regionNames the array of Strings containing the keys in regionsMap (lines 394–395). Next, lines 398–400 create an array of booleans representing whether each region is enabled. Lines 403–405 create an AlertDialog.Builder and set the dialog’s title. Lines 408–410 create the displayNames String array and store in it the region names with underscores replaced by spaces.

Next, we call AlertDialog.Builder method setMultiChoiceItems to display the list of regions. Each region that’s currently enabled displays a check mark in its corresponding checkbox (as in Fig. 6.6). The first two arguments are the array of items to display and a corresponding array of booleans indicating which items should be enabled. The first argument can be either an array of Strings or a resource constant representing an array of Strings. The third argument is the DialogInterface.OnMultiChoiceClickListener that responds to each touch of an item in the dialog. The anonymous inner class (lines 416–427) implements the listener’s onClick method to include or exclude the clicked region, depending on whether or not it’s checked. The method’s second argument represents the index of the item the user touched and the third argument represents its checked state. We use these to put the appropriate updated state information into regionsMap.

Lines 431–440 define the dialog’s positive Button. If the user touches this button, the resetQuiz method is called to start a new game, based on the current game settings. If the user simply touches the device’s back button, the new settings will not take effect until the next quiz begins. Finally, lines 443–444 create the dialog and display it.

Anonymous Inner Class That Implements Interface OnClickListener to Respond to the Events of the Guess Buttons

The anonymous inner class object guessButtonListener implements interface OnClickListener to respond to Button’s events. Line 225 registered guessButtonListener as the event-handling object for each newGuessButton. Method onClick simply passes the selected Button to method submitGuess.


451     // called when a guess Button is touched
452     private OnClickListener guessButtonListener = new OnClickListener()
453     {
454        @Override
455        public void onClick(View v)
456        {
457           submitGuess((Button) v); // pass selected Button to submitGuess
458        } // end method onClick
459     }; // end answerButtonListener
460  } // end FlagQuizGame


Fig. 6.22. Anonymous inner class that implements interface OnClickListener to respond to the events of the answerButton.

6.6. AndroidManifest.xml

In Section 5.6, we introduced the contents of the manifest file. For this app, we explain only the new features (Fig. 6.23). In line 7, we use the android:theme attribute of the application element to apply a theme to the application’s GUI. A theme is a set of styles that specify the appearance of a GUI’s components. In this case, the attribute’s value indicates that the application’s title bar—where the app’s name is normally displayed—should be hidden. For a complete list of predefined styles and themes, see

developer.android.com/reference/android/R.style.html

and for more details on applying styles and themes, see

developer.android.com/guide/topics/ui/themes.html


1   <?xml version="1.0" encoding="utf-8"?>
2   <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3      package="com.deitel.flagquizgame" android:versionCode="1"
4      android:versionName="1.0">
 5      <application android:icon="@drawable/icon"
 6         android:label="@string/app_name"
 7         android:theme="@android:style/Theme.NoTitleBar">
 8         <activity android:name=".FlagQuizGame"
 9            android:label="@string/app_name"
10            android:screenOrientation="portrait">
11            <intent-filter>
12               <action android:name="android.intent.action.MAIN" />
13               <category android:name="android.intent.category.LAUNCHER" />
14            </intent-filter>
15         </activity>
16      </application>
17      <uses-sdk android:targetSdkVersion="10" android:minSdkVersion="8"/>
18   </manifest>


Fig. 6.23. AndroidManifest.xml file for the Flag Quiz Game app.

You can set the application’s theme on the Application tab in the manifest editor. Simply enter the attribute value shown in line 7 into the Theme field.

In the activity element, line 10 uses android:screenOrientation attribute to specify that this app should always appear in portrait mode (that is, a vertical orientation). To set this attribute’s value, select the activity in the bottom left corner of the Application tab in the manifest editor. The manifest options for the activity are displayed at the bottom right side of the Application tab. In the Screen orientation drop-down list, select portrait. After making your changes to the manifest, be sure to save your changes.

6.7. Wrap-Up

In this chapter, we built a Flag Quiz Game app that tests the user’s ability to correctly identify country flags. You learned how to define String arrays in the strings.xml file. You also learned how to load color and String array resources from the colors.xml and strings.xml files into memory by using the Activity’s Resources object.

When the app needed to display a quiz question’s flag, you used the AssetManager to open an InputStream to read from the flag image’s file. Then, you used that stream with class Drawable’s static method createFromStream to create a Drawable object that could be displayed on an ImageView with ImageView’s setImageDrawable method.

You learned how to use the app’s Menu to allow the user to configure the app’s options To specify the Menu options, you overrode Activity’s onCreateOptionsMenu method. To respond to the user’s menu selections, you overrode Activity method onOptionsItemSelected.

To delay displaying the next flag after a correct guess, you used a Handler object postDelayed to execute a Runnable after a 1,000-millisecond delay. When the user made an incorrect choice, the app shook the flag by applying an Animation to the ImageView. You used AnimationUtils static method loadAnimation to load the animation from an XML file that specified the animation’s options. You also specified the number of times the animation should repeat with Animation method setRepeatCount and performed the animation by calling View method startAnimation (with the Animation as an argument) on the ImageView.

You learned how to log exceptions for debugging purposes with Android’s built-in logging mechanism, which uses a circular buffer to store the messages for a short time. You also used various collection classes and interfaces from the java.util package to manage data in the app.

In Chapter 7, you’ll create a Cannon Game app using multithreading and frame-by-frame animation. You’ll handle touch gestures and use a timer to generate events and update the display in response to those events. We also show how to perform simple collision detection.

Self-Review Exercises

6.1. Fill in the blanks in each of the following statements:

a. Files in the assets folders are accessed via a(n) __________ (package android.content.res), which can provide a list of all of the file names in a specified subfolder of assets and can be used to access each asset.

b. A(n) __________ animation moves a View within its parent.

c. By default, animations in an animation set are applied in parallel, but you can use the __________ attribute to specify the number of milliseconds into the future at which an animation should begin. This can be used to sequence the animations in a set.

d. To access the app’s assets folder’s contents, a method should get the app’s AssetManager by calling method __________ (inherited indirectly from class ContextWrapper).

e. A(n) __________ is a set of styles that specify the appearance of a GUI’s components.

6.2 State whether each of the following is true or false. If false, explain why.

a. We use AnimationUtils static method loadAnimation to load an animation from an XML file that specifies the animation’s options.

b. Android does not provide a logging mechanism for debugging purposes.

c. ImageView attribute android:adjustViewBounds specifies whether or not the ImageView maintains the aspect ratio of its Drawable.

d. You load color and String array resources from the colors.xml and strings.xml files into memory by using the Activity’s Resources object.

Answers to Self-Review Exercises

6.1.

a. AssetManager.

b. translate.

c. android:startOffset.

d. getAssets.

e. theme.

6.2.

a. True.

b. False. When exceptions occur, you can log them for debugging purposes with Android’s built-in logging mechanism, which uses a circular buffer to store the messages for a short time.

c. True.

d. True.

Exercises

6.1. Fill in the blanks in each of the following statements:

a. When the user selects an item from a Menu, Activity method __________ is called to respond to the selection.

b. To delay an action, we use a(n) __________ (package android.os) object to execute a Runnable after a specified delay.

c. We also specify the number of times an animation should repeat with Animation method __________ and perform the animation by calling View method startAnimation (with the Animation as an argument) on the ImageView.

d. A(n) is a collection of animations which make up a larger animation.

e. Android supports __________ animations which allow you to animate any property of any object.

f. For the android:fromXDelta attribute, specifying the value -5%p indicates that the View should move to the __________ by 5% of the parent’s width (indicated by the p).

g. We use the __________ attribute of the application element to apply a theme to the application’s GUI.

6.2. State whether each of the following is true or false. If false, explain why.

a. You can use the android:screenOrientation attribute to specify that an app should always appear in landscape mode (that is, a vertical orientation).

b. You specify the number of times an animation should repeat with Animation method repeatCount.

6.3. (Enhanced Flag Quiz App) Make the following enhancements to the Flag Quiz app:

a. Count the number of questions that were answered correctly on the first try. After all the questions have been answered, display a message describes how well the user performed on first guesses.

b. Keep track of the score as the user proceeds through the app. Give the user the most points for answering correctly on the first guess, fewer points for answering correctly on the next guess, etc.

c. Use a SharedPreferences file to save the top five high scores.

d. Add multiplayer functionality.

e. If the user guesses the correct flag, include a “bonus question” asking the user to name the capital of the country. If the user answers correctly on the first guess, add 10 bonus points to the score; otherwise, simply display the correct answer, then allow the user to proceed to the next flag.

f. After the user answers the question correctly, include a link to the Wikipedia for that country so the user can learn more about the country as they play the game. In this version of the app, you may want to allow the user to decide when to move to the next flag.

6.4. (Road Sign Quiz App) Create an app that tests the user’s knowledge of road signs. Your app should display a random sign image and ask the user to select the sign name. See http://mutcd.fhwa.dot.gov/ser-shs_millennium.htm for traffic sign images and information.

6.5. (U.S. State Quiz App) Using the techniques you learned in this chapter, create an app that displays an outline of a U.S. state and asks the user to identify the state. If the user guesses the correct state, include a “bonus question” asking the user to name the state’s capital. If the user answers correctly, add 10 bonus points to the score; otherwise, simply display the correct answer, then allow the user to proceed to the next state. Keep score as described in Exercise 6.3(c).

6.6. (Country Quiz App) Using the techniques you learned in this chapter, create an app that displays an outline of a country and asks the user to identify its name. If the user guesses the correct country, include a “bonus question” asking the user to name the country’s capital. If the user answers correctly, add 10 bonus points to the score; otherwise, simply display the correct answer, then allow the user to proceed to the next country. Keep score as described in Exercise 6.3(c).

6.7. (Android Programming Quiz App) Using the Android knowledge you’ve gained thus far, create a multiple-choice Android programming quiz using original questions that you create. Add multiplayer capabilities so you can compete against your classmates.

6.8. (Movie Trivia Quiz App) Create a movie trivia quiz app.

6.9. (Sports Trivia Quiz App) Create a sports trivia quiz app.

6.10. (Custom Quiz App) Create an app that allows the user to create their own customized true/false or multiple-choice quiz. This is a great study aid. The user can input questions on any subject and include answers, then use it to study for a test or final exam.

6.11. (Lottery Number Picker App) Create an app that randomly picks lottery numbers. Ask the user how many numbers to pick and the maximum valid number in the lottery (set a maximum value of 99). Provide five possible lottery-number combinations to chose from. Include a feature that allows the user to easily pick from a list of five popular lottery games. Find five of the most popular lottery games in your area and research how many numbers must be picked for a lottery ticket and the highest valid number. Allow the user to tap the name of the lottery game to pick random numbers for that game.

6.12. (Craps Game App) Create an app that simulates playing the dice game of craps. In this game, a player rolls two dice. Each die has six faces—we’ve provided die images with the book’s examples. Each face contains one, two, three, four, five or six spots. After the dice have come to rest, the sum of the spots on the two top faces is calculated. If the sum is 7 or 11 on the first throw, the player wins. If the sum is 2, 3 or 12 on the first throw (called “craps”), the player loses (the “house” wins). If the sum is 4, 5, 6, 8, 9 or 10 on the first throw, that sum becomes the player’s “point.” To win, a player must continue rolling the dice until the point value is rolled. The player loses by rolling a 7 before rolling the point.

6.13. (Craps Game App Modification) Modify the craps app to allow wagering. Initialize the variable balance to 1000 dollars. Prompt the player to enter a wager. Check that wager is less than or equal to balance, and if it’s not, have the user reenter wager until a valid wager is entered. After a correct wager is entered, run one game of craps. If the player wins, increase balance by wager and display the new balance. If the player loses, decrease balance by wager, display the new balance, check whether balance has become zero and, if so, display the message "Sorry. You busted!"

6.14. (Computer-Assisted Instruction App) Create an app that will help an elementary school student learn multiplication. Select two positive one-digit integers. The app should then prompt the user with a question, such as

How much is 6 times 7?

The student then inputs the answer. Next, the app checks the student’s answer. If it’s correct, display one of the following messages:

Very good!
Excellent!
Nice work!
Keep up the good work!

and ask another question. If the answer is wrong, display one of the following messages:

No. Please try again.
Wrong. Try once more.
Don't give up!
No. Keep trying.

and let the student try the same question repeatedly until the student gets it right. Enhance the app to ask addition, subtraction and multiplication questions.

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

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