Good apps provide useful functionality and an easy to use interface. The user interface is made of various Graphical User Interface (GUI) components and typically waits for user interaction. Some of these GUI components enable user input, such as clicking on a button, entering data from the keyboard, selecting an item from a list, and spinning a wheel. . . Those user interactions are called events. When an event happens, the app processes the event and updates the screen; this is called handling the event. In this chapter, we learn about various GUI components, their associated events, and how to handle events on those components. We also learn how to style the look and feel of those components. Later, we will learn how to better arrange those components on the screen with layout managers.
Previously, we created our first app, which had two parts:
▸ activity_main.xml, which defined the GUI: this is called the View part of the app.
▸ The MainActivity
class, which displayed the GUI (more generally, the Activity
class manages the GUI); this is called the Controller part of the app.
Our first app only displayed a label, so it did not have any functionality; the functionality of an app is called the Model.
It is good design practice to separate these three parts: the Model, the View, and the Controller. It makes the app much easier to design, code, maintain, and modify later on. We could also reuse parts of the app, for example the Model, to make another app.
In this chapter, we make a simple Tip Calculator app using the Model View Controller architecture; in order to do that, we code three important files:
▸ TipCalculator.java: the TipCalculator
class encapsulates the functionality of a tip calculator; this class is the Model of the app,
▸ activity_main.xml: this file defines the View of the app, and
▸ MainActivity.java: this class is the Controller of the app.
What makes up the functionality of a tip calculator? We keep the Model to a minimum in order to focus more on the View and the Controller. Our minimum Model configuration includes the bill, the tip percentage, and two methods to compute the tip and the total bill. The View mirrors the Model and includes four GUI components to display the data specified in the Model.
The Controller is the middleman between the View and the Model. When the user inputs data in the View—for example, changes the tip percentage—the Controller asks the Model to perform some calculations, and then updates the View accordingly.
More generally, we can use the Model View Controller Store architecture. This is useful when we have persistent data; we can store and retrieve data in and from the Store.
The Model is typically straightforward Java code, and does not include any GUI-related code. It is generally a good place to start.
TABLE 2.1 API for the TipCalculator
class
Constructor | |
---|---|
TipCalculator | TipCalculator(float newTip, float newBill) Constructs a TipCalculator object |
Methods | |
Return value | Method name and argument list |
float | getTip( ) |
float | getBill( ) |
void | setTip( float ) |
void | setBill( float ) |
float | tipAmount( ) |
float | totalAmount( ) |
We start a new Android Studio project, we call it TipCalculatorV0, and we choose the Empty Activity template.
The Model encapsulates the Tip Calculator functionality. The Model should be platform independent. It should be coded so that it could be used to build not just this mobile app or another mobile app, but also a desktop application or even an Internet website.
For this app, the Model is simple and is only composed of one class that encapsulates a tip calculator, the TipCalculator
class. It is a regular Java class. In fact, we can even code this class outside the Android development environment and test it before we bring it into our project. We choose to place our class in the com.jblearning.tipcalculatorv0 package (and directory) of our project, inside the Java directory. We select that directory, then select File → New → Java Class, write the name of the class, and click OK. The class skeleton appears. TABLE 2.1 shows the API for the TipCalculator
class.
EXAMPLE 2.1 shows the class code. In order to code the TipCalculator
class and a Model in general, we do not need to know anything about Android programming. Furthermore, we can test the Model with a simple Java program that tests all the methods of the class; this is left as an exercise. We only have one class in this example, but in general, a Model can include many classes and can be placed in its own directory. The most useful methods for this app are the constructor (lines 7–10), and the tipAmount
(lines 30–32) and totalAmount
(lines 34–36) methods.
We now turn our attention to the View and the capabilities of the Android development environment for designing a GUI.
In this chapter, we use the activity_main.xml file to create and define the GUI components that are displayed on the screen. We can also define and manipulate GUI components entirely by code, which we will do later in the book.
GUI Components are objects that have a graphical representation; they are also called widgets. The term widget
also has another meaning for Android users—it refers to a mini-app that is displayed and is running on the Android’s Home or Lock screen, typically showing the app’s most relevant information. GUI components can:
▸ display information
▸ collect data from the user, and
▸ allow the user to interact with them and trigger a call to a method.
Android provides an extensive set of classes that encapsulate various GUI components. TABLE 2.2 lists some of them.
A lot of GUI components share some functionality, and inherit from each other. FIGURE 2.1 gives a summary of the inheritance hierarchy for some of them.
A View
represents the basic building block for GUI components; the View
class is in the android.view
package. It occupies a rectangular area of the screen, is drawable, and can respond to events. View
is the superclass for GUI components such as buttons, checkboxes, and lists. ViewGroup
is the superclass for layout classes and is also in the android.view
package; layouts are invisible containers that can contain other Views
or other ViewGroups
. Most other classes shown in Figure 2.1, such as ImageView
, TextView
, Button
, EditText
, CompoundButton
, AutoCompleteTextView
, Checkbox
, RadioButton
, Switch
, and ToggleButton
are in the android.widget
package. KeyboardView
is in the android.inputmethodservice
package.
TABLE 2.2 Selected GUI components and their classes
GUI Component Type | Class | Description |
---|---|---|
View | View | A drawable rectangular area on the screen; can respond to events |
Keyboard | KeyboardView | Renders a keyboard |
Label | TextView | Displays text |
Text field | EditText | Editable text field where user can enter data |
Button | Button | Pressable and clickable push button |
Radio button | RadioButton | Two-state button that can be checked by the user; when used in a group, checking a radio button automatically unchecks the other radio buttons in the group |
Checkbox | CheckBox | Two-state button that can be checked or unchecked by the user |
Two-state toggle button | ToggleButton | Two-state switch button that can select between two options |
Two-state toggle button | Switch | Similar to a ToggleButton; user can also slide between the two states |
RelativeLayout, TextView, EditText
, and Button
: Tip Calculator, Version 0In Version 0 of this app, we display some GUI components without any functionality. This version uses four GUI components: two TextView
s that show labels for the bill and tip percentage, and two EditText
s where the user enters the bill and the tip percentage. In this version, we are not processing any user data, we are only concerned with placing the widgets on the screen; we do not even calculate or show the tip amount and the total amount.
In order to keep things simple, we only allow the app to work in vertical orientation, which we can specify in the AndroidManifest.xml file. We do that by assigning the value portrait
to the android:screenOrientation
attribute of the activity
tag at line 13 in EXAMPLE 2.2.
We edit the activity_main.xml file to define the user interface. If the GUI is static and does not change as the user interacts with the app, activity_main.xml is a good way to define it. If the GUI is dynamic and changes as the app is running, then we can create and define the user interface by code. For example, if the app retrieves a list of links from a file and displays them, we do not know in advance how many links we will retrieve and that we will need to display. Typically, whatever we can do via XML attributes and values in the activity_main.xml file, we can also do programmatically.
Before editing the activity_main.xml file of our Tip Calculator app, let’s look at the skeleton that is provided when we create a project. The TextView
element has the following two attributes and values:
android:layout_width="match_parent" android:layout_height="wrap_content"
Android:layout_width
and android:layout_height
are special attributes that can be specified with regular View
attributes, but are instead parsed by the View
’s parent. They belong to the static
class ViewGroup.LayoutParams
, which Views
use to tell their parents how they want to be laid out. ViewGroup.LayoutParams
is a static
class nested inside the ViewGroup
class.
The ViewGroup.LayoutParams
class enables us to specify the width and height of a View
relative to its parent. We can use its android:layout_width
and android:layout_height
XML attributes to specify the width and height of the View
, not only with absolute values, but also relative to the View’
s parent. The class provides two constants, MATCH_PARENT
and WRAP_CONTENT,
to that effect, as seen in TABLE 2.3.
We can assign the android:layout_width
and android:layout_height
XML attributes a match_parent
value or a wrap_content
value. The match_parent
value corresponds to the constant MATCH_PARENT
(whose value is –1) of the ViewGroup.LayoutParams
class, and means that the View
should be as big as its parent, minus the padding. The wrap_content
value, which corresponds to the constant WRAP_CONTENT
(whose value is –2) of the ViewGroup.LayoutParams
class, means that the View
should just be dimensioned big enough to enclose its contents, plus the padding.
If we look at the TextView
of the skeleton code provided in activity_main.xml, we see that we have dimensioned the width and the height of the View
relative to the width and height of its contents.
TABLE 2.3 XML attributes and constants of ViewGroup.LayoutParams
Attribute Name | Description |
---|---|
android:layout_width | Sets the width of the View |
android:layout_height | Sets the height of the View |
Constant | Value |
MATCH_PARENT | –1 |
WRAP_CONTENT | –2 |
We can also dimension the View
with absolute coordinates. If we want to specify absolute dimensions, several units are available: px (pixels), dp (density-independent-pixels), sp (scaled pixels based on preferred font size), in (inches), mm (millimeters).
For example:
android:layout_width=”200dp” android:layout_height=”50dp”
However, an app will eventually be run on many different devices that can have a variety of screen sizes. Thus, we recommend using relative positioning rather than absolute positioning. If we choose to use absolute dimensions, we recommend the dp
units because they are independent of the screen density of the device that the app is installed on.
Furthermore, both android:layout_width
and android:layout_height
attributes are mandatory for each GUI element. Omitting one of them will result in a runtime exception; the app will stop and display the message:
Unfortunately, AppName has stopped
As shown in Figure 2.1, EditText
and Button
are subclasses of TextView
, itself a subclass of View
; therefore, all these three classes inherit the attributes of View
, in addition to having their own. TABLE 2.4 shows some XML attributes of View
, along with the corresponding methods and their descriptions. We can see the full list at http://www.developer.android.com/reference/android/view/View.html.
TABLE 2.4 Selected XML attributes and methods of View
Attribute Name | Related Method | Description |
---|---|---|
android:id | setId( int ) | Sets an id for the View; the View can then be retrieved by code using its id |
android:minHeight | setMinimumHeight( int ) | Defines the minimum height of the View |
android:minWidth | setMinimumWidth( int ) | Defines the minimum width of the View |
The Android Development Kit (ADK) provides layout manager classes to arrange the GUI components on the screen; they are subclasses of ViewGroup
. FIGURE 2.2 shows some of them.
The default layout manager used in activity_main.xml when an empty project is created is a RelativeLayout
. It enables us to position components relative to other components. Typically, a layout manager class has a public static
inner class, often named LayoutParams
, containing XML attributes that enable us to position components. If the class name is C
, then we refer to that inner class as C.LayoutParams
. The RelativeLayout
class has a public static
inner class named LayoutParams
(we refer to it using RelativeLayout.LayoutParams
) that contains many XML attributes that enable us to arrange elements relative to each other, for example below a View, or to the right of another View. In order to reference another View
, we can give that View
an id and use that id to reference it. We can also position a View
with respect to its parent View
, in which case we do not need to reference the parent View
with its id. We list some of these attributes in TABLE 2.5. The first eight attributes listed (android:layout_alignLeft
to android:layout_toRightOf
) expect their value to be the id of a View
. The last four attributes listed expect their value to be true
or false
.
For a value to be the id of a View
, that View
must have been given an id.
The Android framework enables us to give an integer id to a View
using the android:id
XML attribute so we can reference it and also retrieve it by code using its id. The id must be a resource reference and is set using the @+ syntax as follows:
android:id="@+id/idValue"
TABLE 2.5 Useful XML attributes of RelativeLayout.LayoutParams
XML attribute | Description |
---|---|
android:layout_alignLeft | View’s left edge matches the value view’s left edge |
android:layout_alignRight | View’s right edge matches the value view’s right edge |
android:layout_alignBottom | View’s bottom edge matches the value view’s bottom edge |
android:layout_alignTop | View’s top edge matches the value view’s top edge |
android:layout_above | View goes above the value view |
android:layout_below | View goes below the value view |
android:layout_toLeftOf | View goes left of the value view |
android:layout_toRightOf | View goes right of the value view |
android:layout_alignParentLeft | If true, view’s left edge matches its parent’s left edge |
android:layout_alignParentRight | If true, view’s right edge matches its parent’s right edge |
android:layout_alignParentBottom | If true, view’s bottom edge matches its parent’s bottom edge |
android:layout_alignParentTop | If true, view’s top edge matches its parent’s top edge |
EXAMPLE 2.3 shows the activity_main.xml file for Version 0. As compared to a skeleton app, we have added two EditText
(lines 21–30 and 42–51), and another TextView
element (lines 32–40). We use the two TextView
elements as labels to describe the two EditTexts
where we expect the user to enter some data. We want the TextView
and the EditText
for the bill to be displayed on the same horizontal line, and the TextView
and the EditText
for the tip percentage to be displayed on the line below. We do not want any of the four Views
to take either the whole width or height of the screen; thus, all the four Views
use the wrap_content
value for both the android:layout_width
and android:layout_height
attributes (lines 15–16, 23–24, 34–35, 44–45). We also specify that they use font size 28 (lines 18, 28, 39, 49).
TABLE 2.6 The android:textSize and android:hint XML attributes of TextView
XML Attribute | Method | Description |
---|---|---|
android:textSize | void setTextSize( int unit, float size ) | Sets the size of the text |
android:hint | void setHint( int resource ) | Sets the hint text to display |
We give all of them an id (lines 14, 22, 33, 43) so that we can refer to them when we position them relative to each other. We use the first TextView
, storing the bill label, as the anchor element at the top left of the screen. At line 17, we give it a minimum width of 120 pixels; we choose that value so that the text of all the labels that we will position below the bill label will easily fit.
At line 25, we specify that the first EditText
, where the user enters the bill, is to the right of the bill label. At line 26, we specify that its bottom edge is on the same horizontal line as the bottom edge of the bill label. At line 27, we specify that its right edge lines up with its parent’s right edge, so that it extends to the right edge of the screen.
At line 36, we specify that the label for the tip percentage is below the bill label, whose id is label_bill
. At lines 37 and 38, we specify that its left and right edges are lined up with the bill label.
At line 46, we specify that the first EditText
, where the user enters the bill, is to the right of the bill label. At line 47, we specify that its bottom edge is on the same horizontal line as the bottom edge of the bill label. At line 48, we specify that its right edge lines up with the right edge of the EditText
above it, whose id is amount_bill
.
We assign hint values to both EditTexts
at lines 29 and 50; both hint values are defined in the strings.xml file. TABLE 2.6 shows the android:textSize
and android:hint
XML attributes of TextView
, along with its corresponding method and description. Since EditText
is a subclass of TextView
, EditText
inherits these two attributes. The default font size is small, so we set the font size of the TextViews
and EditTexts
to 28. By specifying sp
units, we insure that the font size is screen-density independent. The size of the font has a direct impact on the size of the TextViews
since they wrap around their contents. If we specify a large font size, then the components may not fit on the screen of small devices. In this app, do not worry about that issue. We will learn how to address that issue later in the book.
Both EditText
elements contain one more attribute, android:inputType
(lines 30 and 51), inherited from TextView
; its value defines the type of data that is enabled for this field. TABLE 2.7 lists some selected values. We want to enable the user to enter digits and the dot character only for the bill, so we choose numberDecimal
for the first EditText
element (line 30). When the app is running, we will not be able to enter anything different from a digit or the dot character (in fact, a maximum of one dot character). We want the user to enter digits only for the tip percentage, so choose number
for the second EditText
element (line 51). Try modifying line 30 or 51 using textPassword
and run the app again. We would see that every character we enter is hidden and shows as a small plain circle.
TABLE 2.7 Selected possible values for the android:inputType
XML attribute of TextView
Attribute Value | Description |
---|---|
text | Plain text |
textPassword | Text is hidden; last character shows for a short time |
number | Numeric values only |
numberDecimal | Numeric values and one optional decimal point |
phone | For entering a phone number |
datetime | For entering a date and time |
date | For entering a date |
time | For entering a time |
FIGURE 2.3 shows the current GUI in the Android Studio environment. We can see the two TextViews
and EditTexts
including the two hints: there is not enough space at the top of the screen to see Enter Bill
properly. In Version 1, we fix that, add more GUI elements, and improve the look and feel.
In Version 1, we complete the GUI, adding two labels and two TextViews
to display the tip amount and the total amount, as well as one Button
. In Version 2, we will update the tip and total amount when the user clicks on the Button
. We also add colors, center the text in the EditTexts
, and add margins around the various elements and padding inside them.
TABLE 2.8 shows more XML View
attributes, along with the corresponding methods and their descriptions. There may not be a one-to-one mapping of each XML attribute to a method and vice versa. For example, a single method, such as setPadding
, is used to set the values of several attributes (paddingBottom
, paddingLeft
, paddingRight
, paddingTop
). The padding attributes relate to extra spacing inside
a GUI component, between the border of the component and its content. If we want to add extra space between GUI components (i.e., on the outside
), we can use the margin attributes of the ViewGroup.MarginLayoutParams
class, as shown in TABLE 2.9.
We can add some padding and margin to the first TextView
of Example 2.3, as shown in the following code sequence:
<TextView android:id=”@+id/label_bill” android:layout_width=”wrap_content” android:layout_height=”wrap_content” android:layout_marginTop=”70dp” android:layout_marginLeft=”50dp” android:padding=”10dp” android:minWidth=”120dp” android:textSize=”28sp” android:text=”@string/label_bill”/>
TABLE 2.8 More selected XML attributes and methods of View
Attribute Name | Related Method | Description |
---|---|---|
android:background | setBackgroundColor( int ) | Set the transparency and background color of the View |
android:background | setBackgroundResource( int ) | Set the drawable resource to be used as the background for the View |
android:alpha | setAlpha(float) | Set the transparency value for the View |
android:paddingBottom | setPadding( int, int, int, int ) | Set the padding, in pixels, of the View’s bottom edge |
android:paddingLeft | setPadding( int, int, int, int ) | Set the padding, in pixels, of the View’s left edge |
android:paddingRight | setPadding( int, int, int, int ) | Set the padding, in pixels, of the View’s right edge |
android:paddingTop | setPadding( int, int, int, int ) | Set the padding, in pixels, of the View’s top edge |
TABLE 2.9 Selected XML attributes and methods of ViewGroup.MarginLayoutParams
Attribute Name | Related Method | Description |
---|---|---|
android:layout_marginBottom | setMargins( int, int, int, int ) | Set extra space at the bottom of this View |
android:layout_marginLeft | setMargins( int, int, int, int ) | Set extra space at the left of this View |
android:layout_marginRight | setMargins( int, int, int, int ) | Set extra space at the right of this View |
android:layout_marginTop | setMargins( int, int, int, int ) | Set extra space at the top of this View |
FIGURE 2.4 shows the resulting padding and margin. The top edge of the bill label is 70 pixels from the top edge of the screen (since there is no GUI component above the bill label). Its left edge is 50 pixels from the left edge of the screen (since there is no GUI component to the left of the bill label). There are 10 pixels of free space around the text Bill
.
TABLE 2.10 Selected XML attributes of TextView
XML Attribute | Method | Description |
---|---|---|
android:textColor | setTextColor( int ) | Text color |
android:textStyle | setTypeface( Typeface ) | Style (bold, italic, regular) of the text |
android:gravity | setGravity( int ) | Text alignment within this TextView |
We can also set more text attributes of a TextView
, such as its color, style, or alignment. TABLE 2.10 shows the android:textColor
, android:textStyle,
and android:gravity
XML attributes of TextView
. The android:gravity
attribute relates to the alignment of the text within the TextView
; the default is left-aligned.
TABLE 2.11 shows the possible formats for the value of the android:textColor
XML attribute of TextView
(or any GUI component that inherits from TextView
). The same applies to the android:background
XML attribute of View
. More generally, for an XML attribute of a View
, it is common to have three strategies to set its value:
▸ Specify a value directly
▸ Reference a resource
▸ Reference a theme attribute
We will discuss themes later in this chapter. In this paragraph, we discuss the first two strategies.
TABLE 2.11 Possible formats for a color used with android:textColor or android:background
Description | Format | Explanation |
---|---|---|
6 digits color value | "#rrggbb" | r, g, b are hex digits, representing the amount of red, green, and blue in the color |
Transparency and 6 digits color value | "#aarrggbb" | a is a hex digit representing the transparency value (0 for transparent, f for opaque) |
3 digits color value | "#rgb" | r, g, and b are duplicated |
Transparency and 3 digits color value | "#argb" | a, r, g, and b are duplicated |
Reference to a resource | "@[+][package:]type:name" | [ ] denote an optional field |
Reference to a theme attribute | "?[package:][type:]name" |
To specify a yellow color (e.g., full red, full green, no blue) for the text, we can directly give a value to the android:textColor
attribute in four different ways:
android:textColor=”#FFFF00” android:textColor=”#FFFFFF00” android:textColor=”#FF0” android:textColor=”#FFF0”
If there are only three or four characters in the color string, then each character is duplicated to obtain an equivalent six- or eight-character color string. If there are six characters only, the first two characters specify the amount of red, the next two characters the amount of green, and the last two characters the amount of blue. If there are eight characters, the first two characters specify the opacity of the color, and the last six characters specify the various amounts of red, green, and blue as before. FF means fully opaque and 00 means fully transparent. If the opacity is not specified (if there are three or six characters), the default is fully opaque.
In "FFFFFF00"
, the first two Fs indicate an opaque color; 00 would indicate a fully transparent color. In "FFF0"
, the first F indicates an opaque color; 0 would indicate a fully transparent color.
Another way to specify a color is to use a resource of the form:
"@[+][package:]type:name"
We have already learned that we can externalize string resources as we did in the strings.xml file. We can also externalize other resources such as: bool, color, dimension (using the tag name dimen
), id, integer, integer array (using the tag name integer-array
), and typed array (using the tag name array
).
The brackets, [], in the syntax expression mean that the field is optional.
In order to use this syntax, we externalize some colors in a file, the resource, named colors.xml. The name of the file does not matter, but it is good practice to choose a file name that is meaningful; the file should have the .xml extension and must be located in the res/values/ directory of our project so that the Android framework can find it. Although not using the .xml extension will not prevent our app from running, we recommend that we use the .xml extension for these files.
If we place colors.xml in the wrong directory, for example in the res/layout/ directory, we will have compiler errors.
TABLE 2.12 shows the supported directories inside the res
directory along with their descriptions.
To specify a color that is defined by the string named lightGreen
in a newly created resource file named colors.xml, we write this code:
android:textColor=”@color/lightGreen"
In the colors.xml file, we included the following code (line 8 of EXAMPLE 2.5) in order to define lightGreen:
<color name="lightGreen">#40F0</color>
TABLE 2.12 List of directories supported inside the res directory
Directory | Description of directory contents |
---|---|
animator | XML files defining property animations |
anim | XML files defining tween animations |
color | XML files defining a state list of colors (that can be used to specify different colors for different states of a button, for example) |
drawable | Bitmap files or XML files defining drawable resources (place drawable resources for the app here) |
mipmap | Bitmap files or XML files defining drawable resources (place the icon for the app here) |
layout | XML files defining a GUI layout |
menu | XML files defining menus |
raw | Files saved in their raw form |
values | XML files defining strings, integers, colors, and other simple values |
xml | XML files that can be read at runtime by calling Resources.getXML(resourceIdNumber) |
Color
is the resource type and lightGreen
is its name. The name of the file, colors.xml, although that is the recommended name, is not important; we could choose any other name for it. We can even have several files defining colors. It is mandatory to use a resources
element (line 2 of Example 2.5) as the parent element of color
. If we use a different element instead, we will get an ”invalid start tag” error message.
If we delete the resources
start and end tags, we will get an error message that our XML document is not well formed. Any error in our XML documents will abort the build when we try to compile or run our app.
At compile time, each child of the resources
element is converted into an application resource object at compile time; in colors.xml, resources
has four children, all color
elements. Each one can then be referenced in other files by its name, for example lightGreen
.
EXAMPLE 2.6 shows the updated activity_main.xml file. We have deleted lines 7 to 10 from Example 2.3 (specifying the padding around the app GUI) because we redefined the margins of the various GUI elements and the default padding is no longer needed.
We separate the input related elements, at the top, from the computed related elements, at the bottom, by a red line (lines 63–72). The red line is a View
element (line 64) whose height is 5 pixels (line 68). We use the same positioning attributes that we use earlier in order to position the five new elements in relation to themselves and the existing four elements: android:layout_below
, android:layout_alignLeft
, android:layout_alignRight
, and android:layout_toRightOf
. We line up the four TextViews
on the left of the screen and the red line by specifying that they all line up with the bill label (lines 43, 70, 81, 106). We specify a constant vertical spacing between elements of 20 pixels by using the android:layout_marginTop
attribute (lines 41, 67, 78, 103, 127).
We add the same amount of padding, 10 pixels, to all the TextView
, EditText
, and Button
elements (lines 15, 25, 40, 53, 79, 91, 104, 116, 128). At lines 13–15, we add margins and padding to the bill label, our anchor element.
We set the background of the four TextViews
on the left to light gray at lines 18, 46, 84, 109. We set the background of the computed tip and total to light green at lines 95 and 120. We set the text color of the two EditText
elements to dark blue at lines 32 and 59. We left the default left-alignment for the four TextViews
on the left. We center-align the two EditTexts
and two TextViews
on the right (lines 31, 58, 96, 121). As FIGURE 2.5 shows, Android Studio suggests the possible values for it as we type.
At line 129, we center the button horizontally. At this point, the button does not respond to a user click; we will implement that feature in Version 3.
EXAMPLE 2.7 shows the updated strings.xml file.
We can see the various component sizes, colors, font sizes, and font styles in FIGURE 2.6, which shows the GUI in the Android Studio environment.
In Version 1, many GUI components have the same attributes with the same values. For example, all the nine elements in the activity_main.xml file have a wrap_content
value for both the android:layout_width
and android:layout_height
attributes. The Android Development Kit (ADK) allows us to externalize styles in resources that we can reuse in order to apply a common style to various elements.
The concept of styles is similar to the concept of CSS (Cascading Style Sheets) in HTML pages. A style is a collection of properties (XML attributes) that specify the look and feel of a View
or a window. A style can specify such things as width, height, padding, background color, text color, text size. We can then use a style to specify values of XML attributes for one or several Views
. Styles allow us to separate the look and feel of a View
from its contents.
We can define styles in a file named styles.xml in one of the res/values directories. In it, we can define several styles, each with a list of XML attributes and values; each style has a name that we can use to reference it.
The syntax for defining a style is:
<style name="nameOfStyle" [parent="styleThisStyleInheritsFrom"]> <item name="attributeName">attributeValue</item> . . . </style>
A style can inherit from another style. Inheritance is defined using the parent
attribute, and is optional. The parent
style can be a native Android style, or it can be a user-defined style.
By defining styles this way, we can build an inheritance hierarchy; not only several GUI elements can use the same style, but a style can reuse another style’s attributes via inheritance. A style can also override the value of an attribute that it inherits. We define six styles as shown in FIGURE 2.7. We use InputStyle
for the two EditTexts
: it inherits from CenteredTextStyle
, which itself inherits from TextStyle
, which inherits from TextAppearance
, an existing style from the Android Standard Development Kit (Android SDK). Thus, the text inside the EditTexts
will be centered, have size 28, and be dark blue. Note that the default text color inherited from Text Appearance
is black but is overridden. The two EditTexts
will be sized based on their contents.
EXAMPLE 2.8 shows the styles.xml file. TextStyle
is defined at lines 11–16; it inherits from the Android native style TextAppearance
using the syntax parent=”@android:style/nameOfParentStyle”
. Note that the contents for each item element, for example wrap_content
or 28sp
, are not inside double quotes.
At lines 18–20, LabelStyle
inherits from our own TextStyle
using the syntax parent="nameOfParentStyle”
. Thus, it inherits the four attributes and their values defined in TextStyle
at lines 12–15, android:layout_width
, android:layout_height
,
android:textSize
, and android:padding
. It specifies the light gray color defined in colors.xml for the android:background
attribute at line 19. Thus, any element styled with LabelStyle
will have a light gray background, 10 pixels of padding around its text, which will have size 28. The size of that element will be set based on the text inside it.
At lines 22–24, CenteredTextStyle
also inherits from our own TextStyle
, specifying that the text is centered. At lines 26–28, InputStyle
inherits from CenteredTextStyle
, specifying that the text color is the dark blue defined in colors.xml. At lines 30–32, OutputStyle
also inherits from CenteredTextStyle
, specifying that the background color is the light green defined in colors.xml. Finally, at lines 34–36, ButtonStyle
inherits from TextStyle
, specifying that the background color is the dark green defined in colors.xml.
Not only does an XML style file provide a centralized location to define styles, but it also makes our code better organized and easier to maintain and modify. Often, we will want to have the same styling themes for GUI components of the same type. If all our TextView
components are styled red and we decide to change them to purple, we only have one line of code to change at a single location. We can also reuse styles in many apps. Furthermore, the expertise needed to create and edit a style file is different from the expertise needed to write Java code. If the app is complex and developed by a group of developers, it makes it easier to separate the work and distribute it among the team members based on their respective expertise.
In EXAMPLE 2.9, we style the GUI elements inside activity_main.xml with the styles defined in styles.xml. The result is an identical GUI to the one in Version 1. In order to style a GUI component, we use this syntax:
style=”@style/nameOfStyle”
Writing android:style
instead of style would result in a compiler error.
At lines 11, 29, 58, and 74, we style the four TextView
elements on the left side of the screen with LabelStyle
. At lines 19 and 38, we style the two EditText
elements with InputStyle
. At lines 67 and 83, we style the two output TextView
elements with OutputStyle
. The Button
element is styled with ButtonStyle
at line 89.
If we modify the styles.xml file, for example if we change the android:background
attribute of LabelStyle
from light gray to another color, we will see that change taking place as we run the app again.
A style is typically applied to one or several screens. Often, we will want the Views
in a screen to have the same look and feel. Or we might want all the screens in the app to have the same look and feel. Using a style, we can give an activity, or give the whole app, a theme.
To apply a style to the whole app, we add an android:theme
attribute to the application element in the AndroidManifest.xml file using this syntax:
android:theme=”@style/nameOfStyle”
The AndroidManifest.xml skeleton already includes the preceding syntax using the style AppTheme
. AppTheme
is already declared in the styles.xml skeleton, and we can edit AppTheme
inside styles.xml. We can also use a style from styles.xml, and change line 10 of Example 2.2 as follows:
android:theme=”@style/LabelStyle”
To apply a style to a given activity, we add an android:theme
attribute to the activity element in the AndroidManifest.xml file as follows:
android:theme=”@style/nameOfStyle”
For example, we could insert a line between lines 12 and 13 of Example 2.2 as follows:
android:theme=”@style/OutputStyle”
There is only one activity in our current app, but in a more complex app, there could be many activities, and therefore there could be many activity elements in the AndroidManifest.xml file. Each one can be themed separately.
Now that we have a Model and a View, we can edit the Controller so that our app calculates and displays the tip and the total when the user enters a new amount for either the bill or the tip percentage rate and clicks on the Calculate button.
Clicking on a button is called an event. Executing some code, for example updating the value of a text field, after the event happened is called handling the event. In GUI programming, there can be many types of events: clicking on a button, checking a checkbox, selecting an item from a list, pressing a key, tapping on the screen, swiping the screen, to name a few.
In the activity_main.xml file, we can assign to the android:onClick
attribute of a View
the name of method: that method will be called when the user clicks on a View
. The syntax is:
android:onClick=”methodName”
The method should be in the Activity
class that is controlling the View, in this case MainActivity
. The Button
class inherits from View
, so we can use the android:onClick
XML
attribute within the Button
element in our activity_main.xml file. If we want a method named calculate
to execute when the user clicks on the Button
, we write:
android:onClick=”calculate”
EXAMPLE 2.10 shows the updated part of the activity_main.xml file.
We should code the method in the corresponding context, most likely an Activity
class. If we do not code that method, the app will crash when the user clicks on the button. It must have the following header:
public void methodName(View v)
Inside the method, the View
parameter v
is a reference to the View
that originated the event.
Thus, inside the MainActivity
class, we need to code the calculate
method, and it has the following header:
public void calculate(View v)
Inside that method, we need to access the EditText
elements to retrieve the data input by the user and some of the TextView
elements to update them accordingly.
If a View
had been given an id, we can get a reference to that View
using the findViewById
method of the Activity
class (inherited by the AppCompatActivity
class), described in TABLE 2.13, with the following method call:
findViewById(R.id.idValue)
Now that the activity_main.xml file is defined, we can code the MainActivity
class, shown in EXAMPLE 2.11, and its new method calculate
.
The calculate
method needs to retrieve the amount entered in the two EditText
components, calculate the corresponding tip and total amounts, and update the two output TextViews
accordingly.
At line 12, we declare a TipCalculator
instance variable, tipCalc
. With it, we can call any method of the TipCalculator
class in order to perform various calculations. At lines 6–8, we import the View
, TextView,
and EditText
classes. View
is in the android.view
package; TextView
and EditText
are in the android.widget
package. We need to import them because we use them in the calculate
method (lines 21–51). When calculate
is called, the View
argument is a reference to the View
that originated the event, in this case the button. We output it at line 23 for feedback purposes only. If we try to code the calculate
method without the View
parameter, our code will compile but when we run the app and click on the Calculate button, we will have a runtime exception.
TABLE 2.13 Useful methods of the Activity and TextView classes
Class | Method |
---|---|
Activity | View findViewById( int id )Finds and returns the View that is identified by the id attribute from the XML inflated in the onCreate method (for example, activity_main.xml) |
TextView | CharSequence getText( )Returns the text displayed in the TextView widget |
TextView | void setText( CharSequence text )Sets the text to be displayed in the TextView widget |
At lines 24–27, we retrieve object references to our two EditText
components using the method findViewById
inherited from the Activity
class, passing their ids, R.id.amount_bill
and R.id.amount_tip_percent
. Since the findViewById
method returns a View
, we typecast its return value to an EditText
.
At lines 28–29, we retrieve the data input by the user in the two EditTexts
and assign the two values to the billString
and tipString Strings
; we use the getText
method (TABLE 2.13) of the TextView
class, inherited by the EditText
class. GetText
returns a CharSequence
, so we make an extra method call to the CharSequence’stoString
method to convert each value retrieved from the EditText
to a String
.
The two values are Strings
; we use the parseFloat
and parseInt static
methods of the Float
and Integer
classes to convert them to a float
and an int
in order to process them. Although the exceptions thrown by the parseFloat
and parseInt
methods (lines 37 and 38) are unchecked, it is good practice to use try
and catch
blocks in order to prevent a runtime exception from being thrown in case the data retrieved is not a valid number. At lines 39–41, we update the Model: we set the bill and tip of tipCalc
by calling setBill
and setTip
. At lines 42–44, we ask the Model to call tipAmount
and totalAmount
to compute the tip and total amounts. At lines 45–47, we use the results to update the View: we place the formatted tip and total amounts in the TextViews
using the setText
method from Table 2.13. In this way, the Controller, the MainActivity
class, gathers input from the View to update the Model, and retrieves updated data from the Model to update the View.
We should never get in the catch
block (lines 48–50); indeed, the numberDecimal
and number
input types of the two EditText
specified in activity_main.xml guarantee that the values of the bill and the tip percentage will be a valid floating point number and a valid integer. The numberDecimal
value for android:inputType
only allows the user to enter digits and a maximum of one dot. The number
value for android:inputType
only allows the user to enter digits. However, it is good practice to trap potential errors and validate data even if we are reasonably sure that they will not happen. This practice is called defensive programming. Inside the catch
block (line 49), we could pop up an alert view to inform the user to enter correct data.
In Logcat, we should see some output similar to this:
v = android.support.v7.widget.AppCompatButton{fe17b58 VFED..C.....P.... 280,1063-799,1235}
This shows that the View
argument is indeed a Button
(the only one, clicked by the user; AppCompatButton
is a class that extends Button and supports features of older versions); fe17b58 is a hexadecimal number representing its memory address.
FIGURE 2.8 shows our app running in the emulator after the user enters 154.50 in the bill EditText
and 18 in the tip percentage EditText
and clicks on the Calculate button. By default, when we have at least one EditText
on the screen, the soft keyboard is open.
One may think that we do not really need a complete model, the TipCalculator
class, for this very simple app. However, even for a simple app, it is good practice to use the Model-View-Controller architecture and therefore have a separate model. Over its lifetime, an app can have many versions and increase in complexity, each version requiring new features and functions that can easily be added to the model and used in the controller. For example, we may want to upgrade this app in the future and include the number of guests, and calculate the tip and total bill per guest. We could also format the tip amount and total bill with two decimal digits only. We might also be interested in building a different version of the app with the same functionality but a different GUI. We would just need to update the View and the Controller, but not the Model.
Our app now has some basic functionality, but we should update the tip and total amount any time the user changes the data (i.e., any time the user is typing on the keyboard) even if the user does not click on the Calculate button. In fact, we do not even need the Calculate button. In order to implement that, we have to edit the View (delete the Button
element in the activity_main.xml file) and change the Controller (process any change in the text of the two EditTexts
); there is no change needed in the Model. EXAMPLE 2.12 shows the end of the updated activity_main.xml file. It no longer includes the Button
element. Since we no longer use the Button
, we can also delete the button_calculate String
in strings.xml, the button style in styles.xml, and the dark green color element in colors.xml.
We have already learned that clicking on a Button
is an event. Typing inside an EditText
component, or more generally pressing a key, is also an event. The Android framework provides developers with tools that alert us whenever an event happens or something is changing or has changed inside a GUI component.
Generally, to capture and process an event, we need to do the following, in order:
Write an event handler (a class extending a listener interface).
Instantiate an object of that class.
Register that object on one or more GUI component.
A typical event handler implements a listener interface, which means that it needs to override all the abstract
methods of that interface.
The static OnKeyListener
interface, nested inside the View
class, includes a method, onKey
, that is called when a key event is dispatched to a View
. However, key presses in software keyboards will generally not trigger a call to this method. We want our app to be as universal as possible and work equally well with mobile devices that have either a hardware keyboard or a software keyboard. For that reason, we do not use the OnKeyListener
interface in this app.
The TextWatcher
interface, from the android.text
package, provides three methods that are called when the text inside a GUI component (a TextView
or an object of a subclass of TextView
, such as EditText
) changes, assuming that a TextWatcher
object is registered on the TextView
. TABLE 2.14 shows these methods. As in any interface, these three methods are abstract
.
In order to have a TextWatcher
be notified of any change in the text inside a TextView
, we need to do the following:
▸ Code a class (also called a handler) that implements the TextWatcher
interface
▸ Declare and instantiate an object of that class
▸ Register that object on the TextView
(in this app the two EditTexts
)
TABLE 2.14 Methods of the TextWatcher interface
Method | When Is the Method Called? |
---|---|
void afterTextChanged ( Editable e ) | Somewhere within e, the text has changed |
void beforeTextChanged ( CharSequence cs, int start, int count, int after ) | Within cs, the count characters beginning at start are about to be replaced with after characters |
void onTextChanged ( CharSequence cs, int start, int before, int count ) | Within cs, the count characters beginning at start have just replaced before characters |
In our handler class, we are only interested in the afterTextChanged
method. We do not really care how many characters have changed and where within the text. We only care that something has changed within the text. So we will implement the other two methods, beforeTextChanged
and onTextChanged,
as “do nothing” methods. Because we need to access the two EditTexts
and the two TextViews
, we choose to implement the handler class as a private
, inner class of MainActivity
. When coding a handler, we can also implement it as a separate public
class if we choose to.
EXAMPLE 2.13 shows the complete code for the MainActivity
class. We made one important change to the overall class design. We have added instance variables for the two EditTexts
(lines 14–15). This is not necessary but it is convenient to have direct references to GUI components if we need to access them at various places (when we register the handler and when we process the text changes). This is easier than to use the findViewById
method every time we want to access them. They are instantiated at lines 23–24.
The handler class is coded at lines 57–69. The afterTextChanged
method (lines 58–60) calls the calculate
method (lines 31–55), which is very similar to the previous calculate
method; note that we deleted its View
parameter.
At line 26, we declare and instantiate a TextChangeHandler
object, tch
. At lines 27 and 28, we register tch
with the two EditTexts
, billEditText
and tipEditText
. Thus, any time the user changes the bill or the tip percentage, afterTextChanged
is automatically called; that triggers a call to calculate
, which updates the Model, and then updates the View accordingly.
As we run the app, the tip amount is updated after any key is pressed when either of the two EditText
components is in focus (except when one of the EditTexts
is empty; in that case, we execute inside the catch block and the components are not updated). FIGURE 2.9 shows the app running after the user enters 125.59 for the bill value and starts entering the tip percentage. As soon as the user types 2, the tip and the total amounts are updated.
Instead of coding a separate class for the handler, then instantiate an object of that class and register that object on one or more GUI components, as we did in Example 2.13 (lines 57–69, line 26, and lines 27–28 respectively), some developers like to use anonymous objects instead and write code as follows:
tipEditText.addTextChangedListener( new TextWatcher( ) { // override afterTextChanged, beforeTextChanged // and onTextChanged here } );
While that code looks more compact at first, we feel that it is less readable and less reusable, particularly if we need to have several handler objects or several GUI components that the handlers need to be registered on.
If we look at the overall code for this app, we can start seeing the benefits of the Model-View-Controller architecture and the resulting organization of the code as follows:
▸ Model: TipCalculator
class
▸ View: activity_main.xml
▸ Controller: MainActivity
class
As the apps that we develop get more complex, the benefits of the Model-View-Controller architecture will be more obvious.
A sound design for an app involves using the Model-View-Controller architecture.
The Model is a set of classes that encapsulate the functionality of the app.
The View is the Graphical User Interface (GUI).
The Controller is a middleman between the Model and the View: it takes some inputs from the View, updates the Model, asks the Model to perform some calculations, and updates the View accordingly.
The Android framework provides a variety of components for building a GUI such as labels, buttons, text fields, checkboxes, radio buttons, and lists.
We can use the activity_main.xml file to set up our GUI, including setting up some events.
We can define styles and apply them to views.
We can apply a style, as a theme, to the whole app or to an activity.
A RelativeLayout
enables us to position GUI components in relation to the position of other GUI components.
We can assign an id to a View
in order to retrieve it later using its id with the findViewById
method from the Activity
class. We can also refer to a component using its id in a layout XML file, which is useful if we use a RelativeLayout
.
Event-driven programming consists of using interactive components and event handlers to respond to events generated by user interaction with these components.
If we want to handle the action of clicking on a View
, we can assign the name of a method to the android:onClick
attribute of that View
in the XML layout file. We should then code that method as a void
method accepting a View
parameter in the corresponding Activity
class.
Generally, to respond to a user’s interaction with a component, we need to write a handler class, instantiate an object of that class, and register that object on one or more components.
Event handler classes implement a listener interface and need to override all their abstract
method(s).
Event handlers can be implemented as private
inner classes; this enables the methods of that class to directly access the instance variables of its outer class. Event handlers can also be implemented with anonymous objects or as separate public
classes.
To listen to text changing inside a TextView
, we can implement the TextWatcher
interface from the android.text
package.
MVC stands for
Model View Code
Made View Controller
Model Visual Controller
Model View Controller
GUI components can be used to (check all that apply)
Display data to the user
Let the user interact with the app
Let the user input data
What is the name of the layout manager class that enables us to position GUI components relative to other GUI components?
LinearLayout
AbsoluteLayout
RelativeLayout
PositionLayout
In what package is the View class?
javax.swing
android.widget
java.util
android.view
What is the name of the attribute of style that lets you specify that a style inherits from another style?
superclass
inherits
parent
baseStyle
What is the name of the attribute of item (assuming an item element is nested inside a style element) that lets you specify the color of some text?
android:color
android:text
android:colorText
android:textColor
What is the name of the attribute of either the application or the activity tag that lets you specify a theme for the whole application or for the activity?
android:style
android:theme
android:@style
android:@theme
You have defined a color named myColor in the file colors.xml using the color XML element. Inside the activity_main.xml file, how do you specify that the background color of an EditText element will use the color myColor?
android:background=
”@color/myColor”
android:background=
”@myColor”
android:background=
”color/myColor”
android:backgroundColor=
”@color/myColor”
What is the name of the attribute of the Button element that lets you specify the method being called when the user clicks on the Button?
android:onPress
android:onButton
android:onClick
android:click
What class can we use to detect if there is a change within the text of a TextView?
TextChanger
TextWatcher
ViewWatcher
KeyListener
The following applies to questions 11 to 15
<TextView android:layout_width=”wrap_content” android:layout_height=”wrap_content” />
Add a line of XML so that the text displayed is the value of a string named book (defined in strings.xml).
Add a line of XML so that the background color of the TextView is red.
Add a line of XML so that the text displayed is centered inside the TextView element.
Add a line of XML so that the TextView is centered on a vertical axis within the View containing that TextView element.
Add a line of XML so that TextView uses a style named myStyle defined in styles.xml (using the XML element style).
Inside colors.xml, write the code to define a color named myColor that is green.
Inside styles.xml, define a style named myStyle so that paddingBottom is 10dp, and the background color is red.
Inside MainActivity.java, write the code to retrieve and assign to a TextView object reference a TextView element defined in activity_main.xml and whose id is look.
We have coded a class named MyWatcher extending the TextWatcher interface and declared an object of that class as follows:
MyWatcher mw = new MyWatcher();
Write one statement to register that object on an EditText object named myEditText.
Modify this chapter’s app. Allow the user to input the number of guests and calculate the tip and total per guest.
Write an app that performs an addition. It has three labels and one button. The first two labels are filled with two randomly generated integers between 1 and 10. When the user clicks on the button, the two integers are added and the result is displayed in the third label. The three labels should be styled using the same style. The button should have a red background and a blue text. All four components should be centered vertically. Include a Model.
Write an app that performs an addition. It has two text fields, one label and one button. The user can enter two integers in the text fields. When the user clicks on the button, the two integers are added and the result is displayed in the label. The two text fields should be styled using the same style. The label and the button should also be styled with the same style, but different from the other style. Each style should have a minimum of four attributes. All four components should be centered vertically. Include a Model.
Write an app that simulates a traffic light. It displays a label and a button. The label represents the current color of a traffic light and can be red, green, or yellow. When the user clicks on the button, the label cycles to the next color and simulates running the traffic light. You should look at the documentation of the View class to figure out how to change the color of a View programmatically. The two components should be centered vertically. Include a Model.
Write an app that lets the user create a color using the RGB color model. It displays three text fields and one label. We expect the user to enter integers between 0 and 255 included in the three text fields; if a value is negative, it should be converted to 0; if it is greater than 255, it should be converted to 255. The value of the text fields represents the amount of red, green, and blue of the background color in the label. When the user modifies the number in any text field, the background color of the label should be updated. You should look at the documentation of the View class to figure out how to change the color of a View programmatically. The three text fields should have the same style with a minimum of four attributes. All four components should be centered vertically. Include a Model.
Write an app that checks if two passwords match. It has two text fields, one label and one button. The user can enter two passwords in the text fields, which should be styled as such. When the user clicks on the button, the two passwords are compared for equality and either THANK YOU (if the passwords match) or PASSWORDS MUST MATCH (if the passwords do not match) is displayed in the label. The text fields and the label should have their own separate style, each with a minimum of four attributes. All four components should be centered vertically. Include a Model.
Write an app that evaluates if an email is valid. The app includes an edit text field, a label and a button. The user can enter his or her email in the edit text field. When the user clicks on the button, the app checks if the email entered contains the @ character. If it does, the label displays VALID, otherwise it displays INVALID. The text field and the label should have their own separate style, each with a minimum of four attributes. The text field and the label should be on the same horizontal axis; the button should be below them and centered. Include a Model.
Write an app that evaluates the strength of a password. It has a text field and a label; the user can enter his or her password. The label displays, as the user types, WEAK or STRONG. To keep things simple, we define a weak password as a string of eight characters or fewer; a strong password is defined as a string of nine characters or more. The text field and the label should have their own separate style, each with a minimum of four attributes. The two components should be centered vertically. Include a Model.
Write an app that asks the user to enter a password with a minimum strength level. It has a text field and a label. As the user enters his or her password, the label displays VALID or INVALID. For the purpose of this app, we define a valid password as a string of eight characters or more with at least one uppercase letter, one lowercase letter, and one digit. The text field and the label should have their own separate style, each with a minimum of four attributes. The two components should be centered vertically. The label displaying VALID or INVALID should be updated as the user types. Include a Model.
Write an app that keeps track of the calories for a meal. Present the user with a choice of several foods; assign a number of calories to each food choice. After the user enters the number of servings for each food, the total number of calories is updated. The text fields and the labels should have their own separate style, each with a minimum of four attributes. Include a Model.
Write an app that keeps track of the calories burned during a workout session. Present the user with a choice of workouts; assign a number of calories to each workout. After the user enters a number for each workout, the total number of calories burned is updated. The text fields and the labels should have their own separate style, each with a minimum of four attributes. Include a Model.
44.200.94.150