Previous  | Next  | Home

Click Tester


 

This short project will introduce some additional aspects of screen views, widgets, and associated event handlers that are commonly used in Android. In the process, we shall learn something about

These skills, added to those already developed in WebView Demo, will provide a strong foundation for developing more ambitious Android interfaces.

 

Creating the Project in Android Studio

Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,


Application Name: ClickTester
Company Domain:< YourNamespace >
Package Name: <YourNamespace> . clicktester
Project Location: <ProjectPath> ClickTester
Target Devices: Phone and Tablet; Min SDK API 15
Add an Activity: Empty Activity
Activity Name: MainActivity (check the Generate Layout File box)
Layout Name: activity_main

where you should substitute your namespace for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to the directory where you will store this Android Studio Project (/home/guidry/StudioProjects/ in my case). If you have chosen to use version control for your projects, go ahead and commit this project to version control.

 

Defining Some Color and String Resources

Edit the file app/res/values/colors.xml to define the colors


<?xml version="1.0" encoding="utf-8"?> <resources> <color name="redback" >#aaff0000</color> <color name="greenback">#aa00ff00</color> <color name="blueback">#aa0000ff</color> <color name="yellowback">#aaffff00</color> </resources>

Then add strings to the file app/res/values/strings.xml to give


<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Click Tester</string> <string name="buttonString">Button</string> <string name="shortclick">Short click:</string> <string name="longclick">Long Click:</string> <string name="redString">Top left block</string> <string name="greenString">Top right block</string> <string name="yellowString">Bottom left block</string> <string name="blueString">Bottom right block</string> </resources>

Now we are ready to define the layout for our main screen.

 

Declaring the Layout in XML

Open the file app/res/layout/activity_main.xml and edit it to read.


<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/buttonString" android:textSize="18sp" /> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"> <View android:id="@+id/redblock" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/redback" android:layout_weight="1"></View> <View android:id="@+id/greenblock" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/greenback" android:layout_weight="1"></View> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1"> <View android:id="@+id/yellowblock" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/yellowback" android:layout_weight="1"></View> <View android:id="@+id/blueblock" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@color/blueback" android:layout_weight="1"></View> </LinearLayout> </LinearLayout>

Much of this should be familiar from the layout for the previous WebViewDemo project (except that there we used a RelativeLayout but here a LinearLayout will be employed):

  1. The parent container is a LinearLayout, with an android:orientation="vertical" property, so its children will be arrayed vertically.

  2. The parent container has three children: one Button and two additional LinearLayouts. Each of the child LinearLayouts has an android:orientation="horizontal" property, so they will array their children horizontally.

  3. Each of the child LinearLayouts contains two Views, and since each of the Views has the property android:layout_weight="1" set, each should have equal area weight in the layout.

  4. The attribute value fill_parent suggests that an object expand to fill its parent container (minus padding), while the attribute value wrap_content means that the object wants to be just big enough to enclose its content plus any padding.

  5. The properties like android:background="@color/redback" set color backgrounds for the four Views, with @color/colorname referencing the color colorname defined previously in app/res/values/colors.xml.

For an overview of layouts, see Common Layouts.

 

Let's See What It Looks Like

If you now run the project in an emulator or on a device by clicking the Run button, the layout that results should look like the following figure.



This is pretty (at least in a tacky sort of way), but it doesn't yet do anything. Let's fix that.

 

Adding Event Handling

A major theme in most applications will be that the user must interact with widgets displayed on the screen, and the app must respond in an appropriate way. Let's now demonstrate that it is relatively easy to get each of the five objects that we have put on the screen (four Views and one Button---which is a subclass of View) to respond to basic user interactions. Open app/java/<YourNamespace>.clicktester/MainActivity.java (where you should substitute your namespace for <YourNamespace>) and edit it to read


package <YourNamespace>.clicktester; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements OnClickListener, OnLongClickListener{ View view; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Set click and long-click listeners View button1 = findViewById(R.id.button1); button1.setOnClickListener(this); button1.setOnLongClickListener(this); View redblock = findViewById(R.id.redblock); redblock.setOnClickListener(this); redblock.setOnLongClickListener(this); View greenblock = findViewById(R.id.greenblock); greenblock.setOnClickListener(this); greenblock.setOnLongClickListener(this); View yellowblock = findViewById(R.id.yellowblock); yellowblock.setOnClickListener(this); yellowblock.setOnLongClickListener(this); View blueblock = findViewById(R.id.blueblock); blueblock.setOnClickListener(this); blueblock.setOnLongClickListener(this); } // Handle short clicks @Override public void onClick(View v) { String whichOne = whichWidget(v); Toast.makeText(this, getString(R.string.shortclick) + " "+whichOne, Toast.LENGTH_SHORT).show(); } // Handle long clicks @Override public boolean onLongClick(View v) { String whichOne = whichWidget(v); Toast.makeText(this, getString(R.string.longclick) +" "+ whichOne, Toast.LENGTH_SHORT).show(); return true; } // Method to determine which widget was pressed private String whichWidget(View v){ String whichOne = ""; switch(v.getId()){ case R.id.button1: whichOne = getString(R.string.buttonString); break; case R.id.redblock: whichOne = getString(R.string.redString); break; case R.id.greenblock: whichOne = getString(R.string.greenString); break; case R.id.yellowblock: whichOne = getString(R.string.yellowString); break; case R.id.blueblock: whichOne = getString(R.string.blueString); break; } return whichOne; } }

In this code, some things should be familiar from our earlier WebViewDemo discussion and some are new.

  1. The class extends AppCompatActivity (which extends FragmentActivity, and that extends Activity ) and implements two interfaces, OnClickListener and OnLongClickListener, which requires that the methods onClick (View v) and onLongClick (View v), respectively, be implemented by the user.


  2. A Java class is permitted to implement any number of interfaces. Syntax: the implements keyword is followed by a comma-separated list of interfaces implemented by the class. If the class subclasses another class, by convention the implements clause follows the extends clause. Interfaces are "functionality contracts"; this generally requires that a user employing an interface must provide explicit definitions for methods that are abstract (without bodies) in the interface definition.


  3. The setContentView(R.layout.activity_main) sets the layout of the initial screen to that specified by res/layout/activity_main.xml.

  4. Similar to the case for the Buttons in WebViewDemo, we have used findViewById(int) to sort out the five widgets arrayed on the screen, attached a click listener to each, and also attached a long-click listener to each.

  5. Although in a simple case like this it would probably be easier to hardwire strings into the Java code, we have followed good Android coding practice and externalized all string and color resources in XML files (see the appendix Managing Android Resources). Thus all references to strings are to the integers R.id.something identifying individual external resources.

  6. For both the onClick and onLongClick methods we use the method whichWidget(View v) to sort out which widget was touched by switching on the integer returned by the getId() method of View and respond accordingly with a transient popup message indicating what event has occurred using the Toast class.

  7. Statements like whichOne = getString(R.string.buttonString) use the getString(int) method of Context (superclass of Activity) to return the string buttonString that is defined in the resource file res/values/strings.xml

An overview of how external resources are accessed in Android may be found in the appendix Managing Android Resources.

 

Try It Out with Event Handling

Now if you execute the app a long or short press on any of the five widgets in our layout should produce an appropriate Toast display indicating the nature of the action. The figure below illustrates.



A short finger tap on the yellow block at the lower left has just been completed. So we see that by this basic approach of laying out widgets and attaching event listeners we can listen for short and long finger taps on the display, sort out which widget was tapped, and take a corresponding action.

 

Adding an Option to Change Colors

In the preceding example we have been content to put a transient message on the screen indicating which screen widget was short-pressed or long-pressed, but in a realistic application we would often want to use such an event to elicit a user response. One way to do that is to pop up a dialog window floating over the original window into which we could place new widgets with which the user can interact. Let's demonstrate how to do that by giving an option of using a long-press on a block to pop up a window allowing the color of that block to be changed. First let's define some new strings that we will need. Open app/res/values/strings.xml and add


<string name="white_label">White</string> <string name="black_label">Black</string> <string name="gray_label">Gray</string> <string name="red_label">Red</string> <string name="green_label">Green</string> <string name="processMenu_title">Change Color</string>

inside the <resources></resources> tag. Next, let us create a string array that we shall need. Right-click on app/res/values, select New > XML > Values XML File, and give it the name arrays (no .xml extension; AS will add that). Edit the new file app/res/values/arrays.xml so that it reads


<?xml version="1.0" encoding="utf-8"?> <resources> <array name="colornames"> <item>@string/white_label</item> <item>@string/black_label</item> <item>@string/gray_label</item> <item>@string/red_label</item> <item>@string/green_label</item> </array> </resources>

This defines an array of strings that can be addressed by the name colornames, and that references the strings just created in strings.xml using the @ syntax. Now we do some editing of MainActivity.java. First add some imports that will be needed:

import android.app.AlertDialog; import android.content.DialogInterface; import android.graphics.Color;

then add after the method whichWidget(View v) the method stub


// Menu with list of color options private void doMenu(View v, int index){ }

(which will be filled out shortly) and then add the following method


// Open AlertDialog holding color options menu and process with anonymous inner class private void processMenu(final View v){ new AlertDialog.Builder(this).setTitle(R.string.processMenu_title) .setItems(R.array.colornames, new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialoginterface, int i){ doMenu(v, i); } }).show(); }

The content within the method processMenu(View) will probably seem a little unusual if you are new to Java. This is an example of an anonymous inner class, which means exactly what the name says:
  • Java permits one class to be defined inside another class; the class inside the other class is called an inner class.

  • By using a construction as in the above example, it is possible to define and use an inner class without ever giving it an explicit name; such a class is called an anonymous inner class.
Inner classes are useful in Java when some new class functionality is required that is relevant only within a single already-existing class. But if that functionality is intermittent, as in the above example of opening a dialog window that presents the user a few choices and will then be closed once the user has interacted with it, a sleek, economical (albeit not very transparent) way to implement it is through an anonymous inner class. If you find the syntax confusing (I do), just view this as boilerplate that is very common in Java for processing dialogs and other similar tasks.

In this method we use AlertDialog.Builder to construct an AlertDialog window in which the user will be able to choose some colors.


A dialog is a small window prompting for a decision or additional information. It is normally used for modal events (events that require user action before proceeding). Dialogs are objects of the Dialog class.

An AlertDialog subclasses Dialog and creates a dialog window of particular form that is well-suited to display an alert (WARNING! This action will arm the nuclear weapon!), and to accept a user decision on how to respond to the alert. A more extensive discussion of using dialogs may be found in the Dialogs developer guide.

Finally, modify the onLongClick(View v) method to add a call to the new method processMenu():


// Handle long clicks @Override public boolean onLongClick(View v) { String whichOne = whichWidget(v); // If it wasn't the button if(whichOne != getString(R.string.buttonString)){ processMenu(v); // If it was the button } else { Toast.makeText(this, getString(R.string.longclick) +" "+ whichOne, Toast.LENGTH_SHORT).show(); } return true; }

where the logic allows changing the color of the blocks but prevents changing the background color of the Button. if you run the app and long-press one of the color blocks you should get a popup window with color choices that looks like the following figure



Now fill in the doMenu method stub in MainActivity.java so that choosing one of these colors changes the color of the background for the view that was pressed. Edit doMenu(View v, int index) to read


// Menu with list of color options private void doMenu(View v, int index){ switch(index){ case 0: // Android White v.setBackgroundColor(Color.WHITE); break; case 1: // Android Black v.setBackgroundColor(Color.BLACK); break; case 2: // Android Gray v.setBackgroundColor (Color.GRAY); break; case 3: // Original red, user-defined in colors.xml v.setBackgroundColor(Color.parseColor(getString(R.color.redback))); break; case 4: // Original green, user-defined in colors.xml v.setBackgroundColor(Color.parseColor(getString(R.color.greenback))); break; } }

In this method index is an integer counting from zero that defines which choice from the array colornames the user selected, and the View method setBackgroundColor (Color c) is used to change the background color of the selected View. We have given two examples of how to specify a color:

  1. In the first three choices the Color class has been used to specify colors through class constants such as Color.BLACK.

  2. In the last two choices we have exhibited how to retrieve a color from the XML resource files for use in code through a two-step process: (1) First convert the int resource identifiers such as R.colors.redback to strings using getString(int), and then convert to a color int using the static Color method parseColor (which requires a String argument and returns an int defining a color).

Now you should find that long-pressing one of the blocks and choosing a color from the resulting popup menu changes the color of that block to the new color, but long-pressing the button will only pop up a message with no color change.


The complete project for the application described above is archived on GitHub at the link ClickTester. Instructions for installing it in Android Studio may be found in Packages for All Projects.

Last modified: July 25, 2016


Previous  | Next  | Home