Android Development Tutorials
Android Development Tutorials
Fundamentals
n
Jason Ostrander
Android UI
Fundamentals
Develop and DesIgn
Jason Ostrander
Android UI Fundamentals: Develop and Design
Jason Ostrander
Peachpit Press
1249 Eighth Street
Berkeley, CA 94710
510/524-2178
510/524-2221 (fax)
Notice of Rights
All rights reserved. No part of this book may be reproduced or transmitted in any form by any means,
electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the
publisher. For information on getting permission for reprints and excerpts, contact permissions@peachpit.com.
Notice of Liability
The information in this book is distributed on an “As Is” basis without warranty. While every precaution has
been taken in the preparation of the book, neither the author nor Peachpit shall have any liability to any
person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by
the instructions contained in this book or by the computer software and hardware products described in it.
Trademarks
Android is a trademark of Google Inc., registered in the United States and other countries. Many of the
designations used by manufacturers and sellers to distinguish their products are claimed as trademarks.
Where those designations appear in this book, and Peachpit was aware of a trademark claim, the designa-
tions appear as requested by the owner of the trademark. All other product names and services identified
throughout this book are used in editorial fashion only and for the benefit of such companies with no
intention of infringement of the trademark. No such use, or the use of any trade name, is intended to
convey endorsement or other affiliation with this book.
987654321
I could write an entire book thanking people for their help along the way. Instead,
I’ll have to settle for this short paragraph:
Thanks to Chris H. for pushing me to consider writing a book and giving me
endless encouragement and support. To Cliff C. for giving me the chance to write
this book. To Robyn T. for keeping me on schedule despite my best efforts. To
JBL for fixing my code and rocking a mean bass guitar. To Scout F. and Myrna V.
for working tirelessly when I was late getting chapters to them. To Lucas D. and
Rob S. for reading early chapters and giving me valuable feedback. To the entire
team at doubleTwist for their dedication to making great Android software. To
the Android team at Google for creating a great platform. To my family for their
continuing support despite my dropping off the face of the earth. To Peachpit for
giving me the opportunity to write this for you. And to you, the reader, for giving
me the chance to teach you in whatever small way I can.
Bio
Jason Ostrander is a web and mobile software developer working at Silicon Valley
startup doubleTwist, where he makes syncing media to Android phones simple.
Prior to that, he solved networking problems at energy management startup
Sentilla and defense company Northrop Grumman. Jason holds an MS in electrical
engineering from UCLA. He lives with his wife in San Francisco’s Mission District,
where he spends his time searching for the perfect chile relleno.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii
Welcome to Android. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x
Contents v
Part 2 The vIew FRAmewORk
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
ii
introduction
introdUCtion Ix
welcome to Android
Throughout this book, you’ll be writing your code using the Eclipse integrated develop-
ment environment (IDE). You’ll need to install the Android software development kit
(SDK), along with the Android Developer Tools (ADT) plugin. This setup includes several
other utilities to help you develop and test your application. Aside from the SDK, none of
these tools are required, but they will make your application development easier.
the tools
The following tools are used throughout this book to build, test, and analyze your
applications.
i
This page intentionally left blank
Part 1
UI
1
GETTInG stArted
Android is a powerful mobile operating system, built
using a combination of the Java programming language
and XML-based layout and configuration files. This chap-
ter introduces the Android development environment, walks
through the basic Hello World application, and covers the Android
tools, with an emphasis on the user interface (UI) tools available in
the Android Developer Tools (ADT) plugin. In this chapter you’ll
create a Hello World application; learn the Android project layout
and purpose of each file and folder; learn basic Android UI con-
cepts like the Activity class and XML layouts; and discover the
tools available for creating user interfaces in Android.
3
Hello World
Before you create a basic Hello World app, you must download and install the
Android developer tools available at developer.android.com. You will need to install
the Android software development kit (SDK), Eclipse, and the ADT plugin. Follow
the directions provided on the developer website to set up the Eclipse develop-
ment environment. All examples in this book use Android SDK Release 13 and the
Eclipse Helios release.
When ready, create the Hello World application using the following steps:
1. Start Eclipse.
2. Open the Java perspective by choosing Window > Open Perspective > Java.
4. Leave all the defaults. Name the project Example and click Next (Figure 1.1).
6. Enter the package name com.example (Figure 1.3). FIGURe 1 .3 The Android
project properties (right)
7. Click Finish, and the project will be created for you.
8. Run your app by right-clicking the Example project in the left-hand Package
Explorer pane and selecting Run As > Android Application.
Hello World 5
FIGURe 1 .4 The AVD Manager
9. Select Yes when prompted to create a new Android Virtual Device (AVD).
This will open the AVD Manager (Figure 1.4). Click the New button and
configure it as shown in Figure 1.5. Click Create AVD when finished.
10. In the AVD Manager, select the AVD you just created and click Start. When
prompted, click Launch. The emulator will start.
11. Once the emulator has loaded, close the AVD Manager, and the Android
Device Chooser will open (Figure 1.6). Select the running emulator, and
click OK.
Hello World 7
FIGURe 1 .7 Hello World app
running on Android emulator
If you want to run the Example app on your phone, follow these steps:
1 . On your phone’s home screen, press Menu > Settings > Applications. Select
the “Unknown sources” checkbox to enable installation from your computer.
2 . Open the Development menu and select the “USB debugging” checkbox.
4 . now close the emulator on your computer and run your application
again. It should install on your phone. If the Android Device chooser
dialog appears, select your device from the list.
The Eclipse IDE created an initial project structure for you when you started a
new Android project. This project contains all the elements you need to build
your application, and you should generally place all your work in this project. It’s
possible to create a library project for code-sharing between applications, but for
most apps this is unnecessary. This section covers the basic structure of the project
folder and where you should place your code, layouts, and resources.
Folder struCture
Expand the Example project folder in the Package Explorer and take a look at the
project structure. Android uses a standard Java application layout. Table 1.1 sum-
marizes the project structure.
item n
src/ This folder contains your app’s Java source code. It follows the standard Java package
conventions. For example, a com.example.Foo class would be located in the folder
src/com/example/Foo.java.
res/ This folder contains all the resources of your app and is where you declare the layout
using XML. This folder contains all layout files, images, themes, and strings.
gen/ This folder is auto-generated when you compile the XML layouts in res/. It usually
contains only a single file, R.java. This file contains the constants you need to reference
the resources in the res/ folder. Do not edit anything in this folder.
assets/ This folder contains miscellaneous files needed by your application. If your app needs a
binary asset to function, place it here. To use these files, you need to open them using
the Java File application programming interfaces (APIs).
AndroidManifest.xml The manifest contains essential information about your app that the Android system
needs. This includes the activites and services your app uses, the permissions it requires,
any intents it responds to, and basic info like the app name.
<uses-feature android:name=”android.hardware.touchscreen”
p android:required=”true” />
The manifest is where you declare the icons and labels used by your application.
You can assign these attributes to many of the XML elements in the manifest. The
most important is the top-level <application> element. This is what will represent
your application on the device home screen and app drawer. However, the icon/label
combination doesn’t just apply to the <application> element. You can use them on
the permissions element, which displays when the user accepts your application
to install. You can place them on the <activity> element, and the user will see
them in the process monitor. These elements are inherited by any sub-components.
Hence, if the <application> icon and label are set, but the <activity> and <intent>
icons and labels are not set, then those elements will use the <application> icon
and label by default. This setup allows you to use component-specific icons and
labels for informing the user of your application’s functions.
item n
resourCes
Android apps store all resources in the res/ folder. What are resources? Basically,
resources are anything that isn’t Java code. Images, layout files, app strings, localized
strings, themes, and even animations go in the res/ folder. Android uses the direc-
tory structure to separate resources for use in different device configurations. In the
Hello World app, there are three drawable folders: drawable-ldpi, drawable-mdpi,
and drawable-hdpi. These represent low-, medium-, and high-density resources.
At runtime, the Android system will select the proper resource based on the device
hardware. If no resource matches, it will select the most closely matching resource.
This will be covered in depth in Chapter 3.
You should never use string literals in your Java code or XML layouts. Always
declare any user-visible strings in the strings.xml file. This makes it easier to
localize your resources later. When using these strings in your app, you reference
them by the name attribute of the string element.
The res/layout/ folder also contains the XML files that declare your applica-
tion layout. Android UI can be created using either XML or Java code. It’s recom-
mended to use XML for layouts, because it provides a good separation between
UI and application logic. Folder names are used to separate layouts for different
device configurations.
The user interface (UI) is the connection between your app and your users. In fact,
to the user, the UI is the app. The Android UI framework is powerful enough to cre-
ate complex UIs with graphics and animations, but it also has enough flexibility to
scale from small-screen handheld devices to tablets to TVs. This section covers the
basics of Android UI development so you can start to create great UIs for your apps.
A key component of the Android UI is the notification tray (Figure 1.9). You
access the tray by touching the status bar at the top of the screen and sliding your
finger down. Android displays a list of all notifications in the notification tray:
new mail notifications, currently playing music, system status info, and any long-
running tasks such as downloads. Tapping a notification in the list typically opens
the app that generated the notification.
Note: You should be aware that the user could replace the stock
android home screen with an alternative home screen. Generally,
these alternatives follow the same ui conventions as the stock android
home screen. however, a few alternative home screens use radically
different ui conventions, so it’s a good idea not to rely on any particular
home screen feature in your app.
Android Ui BAsiCs 15
xml laYout
Android defines user interfaces using a combination of XML layout files and Java
code. You can use Java to specify all layouts, but it’s generally preferable to use
XML to take advantage of Android’s automatic resource selection. This allows you
to declare layouts for different hardware configurations, and the Android system
will select the most appropriate layout automatically.
Here is the code in the Hello World application’s main.xml file.
The first line is basic XML boilerplate, listing the version and encoding. This
is always the same and must be included at the beginning of every layout file.
The next line defines one of Android’s basic container types, the LinearLayout.
This view arranges its child views linearly inside it. You will learn more about
LinearLayouts in the next chapter.
tip: even when you plan to set the text of a TextView in code,
it’s a good idea to declare a default string. that way, you can see
what your layouts will look like with full text.
Android Ui BAsiCs 17
package com.example;
import android.app.Activity;
import android.os.Bundle;
public class ExampleActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
Android activities use a callback structure for creating UI events. The framework
calls the appropriate overridden method during the creation or destruction of the
activity. In the example activity, only a single method is implemented: onCreate.
This method is called when an activity is first created. Typically this is where the
setup of the activity takes place. This is where you create your views and set up
any adapters you need to load your data. It’s not strictly necessary to override any
of the activity methods but onCreate.
The example just sets the view for the activity. It does this by calling
setContentView(R.layout.main). This references the R.java file that the Android
developer tools built for you. In this case, it tells the system to load the main.xml
file located in one of the layout directories. The Android runtime selects the most
appropriate main.xml file (there is only one in this case) and loads it.
hardWare ButtoNs
Android devices prior to version 3.0 have four hardware buttons: Home, Back,
Menu, and Search. Android version 3.0 and above have made the hardware buttons
optional. In their place, Android presents onscreen software buttons that replicate
the functionality of hardware buttons (Figure 1.10).
The Home button takes the user to the phone home screen. It’s not generally
available to applications, unless the app is a home-screen replacement.
The Back button is meant to navigate back in the Android activity stack. This
allows the user to easily jump into an app and them immediately return to the
previous screen.
Android Ui BAsiCs 19
FIGURe 1 .11 The options menu
on the Android home screen
The Menu button displays a context-dependent list of options (Figure 1.11). Use
the options menu for displaying infrequently used options for your application.
On Android tablets and phones running version 3.0 or greater, this button is not
available, and options are instead presented on the action bar. You’ll learn about
the differences between Android 4.0 devices and previous versions of Android in
a later chapter.
Note: Not all android devices have all of these buttons. in particular,
android 4.0 devices omit the search button, making it impossible for users
to search in applications. take care to evaluate the necessity of
search to your application, and provide a button in your ui if search is
an essential feature.
Android Ui BAsiCs 21
tools
The Android SDK includes a suite of tools to assist in developing your apps. The
suite consists of SDK tools and platform tools. The SDK tools, including ADT, are
platform independent and are used regardless of which Android version you are
developing against. The platform tools are specific to Android versions and are
generally installed when updating the SDK to a new version. Let’s focus on the SDK
tools, specifically those used to develop the UI of an Android app.
1 The Configuration drop-down menu lets you change the way the layout
is displayed. This is a quick way to view your UI for different hardware
configurations, such as phones and tablets.
2 The Canvas displays the current layout as it will appear on your specified
device configuration. The layout includes a set of context-specific buttons
for quickly changing the parameters of selected views. You can drag views
from the Palette and drop them here to build the UI. You can right-click
components to get a context-specific list of available configurations. You
can also use this list for refactoring the UI into reusable components.
3 2
3 The Palette contains the basic building blocks of Android user interfaces. FIGURe 1 .12 The graphical
This is where you can find the basic layout containers, the form controls layout editor
(including buttons and text inputs), and even advanced features like transi-
tion animations. You can drag each of these components onto the Canvas to
create your UI. When you drag components onto the Canvas, they will snap
to the edges of the existing blocks, helping to align your layout.
4 The Outline displays an overview of your layout, with all components listed
in a hierarchy. This makes it easy to see how the components are nested. It
also makes finding hidden or invisible components easier. You can use this
view to quickly reorder the components of your layout.
5 At the bottom of the graphical layout editor are tabs for switching to a
standard XML view of your UI. While you can accomplish a lot using the
graphical layout editor, it’s recommended that you tweak your final layouts
by hand-coding the XML.
tools 23
FIGURe 1 .13 Package Explorer
pane showing the res/
folder (top)
The graphical layout editor is very powerful, and you should spend some time
getting to know the options available within it. Let’s experiment with the editor
by adding a few buttons and text boxes to the layout.
1. In the Eclipse Package Explorer, expand the res/layout folder of the project.
2. Right-click the file named main.xml and select Open With > Android Layout
Editor (Figure 1.13).
This will display the graphical layout editor. You may need to set up a device
configuration before you can start editing. At the top of the window are the
controls for specifying the Android version, theme, locale, screen size, and
orientation of the device.
3. Configure the options as seen in Figure 1.14. You may need to close main.xml
and reopen it for the changes to take effect.
4. Now try dragging a TextView onto the layout, just below the existing
TextView.
Notice that you can place the view only above or below the existing view.
Remember the LinearLayout container from before? It was set up with a
vertical orientation, so you can arrange views only vertically within it. Now
try changing the size of the TextView.
6. Add a Button below your newly created TextView, and expand it to fill the
width of the window.
You should now have something that looks like Figure 1.15. As you can
see, the graphical layout editor makes it possible to quickly create complex
layouts with very little effort.
tools 25
aNdroid Virtual deViCes
Android is designed to run on a wide range of hardware. It’s important to test your
code extensively before release to ensure that your app is compatible with most
Android devices. This is where the Android Virtual Devices, or AVDs, come in.
An AVD is an emulated Android device. It’s not just a simulator; it actually runs
the full Android framework, just as an actual device would. This is an important
distinction, and it makes the emulator a far better representation of real-world
devices than a simulator.
Because AVDs are emulated devices, they run the standard Android graphics
stack. This can be very slow for high-resolution AVDs such as tablets. Google is
working on improving this, but for now it’s recommended to test your layouts
in the graphical layout editor and only use the emulator for final verification. Of
course, you can always use an actual Android device.
You already created an AVD when you ran the Hello World application. You did
this using the AVD Manager. Using the AVD Manager, you can create a range of
emulated devices with different hardware characteristics, including
J Screen size and default orientation
J Hardware support, such as accelerometers and gamepads
J The Android OS version
J SD card storage, emulated using your hard disk
tip: the emulator is useful for testing your app, but it cannot
emulate all possible hardware features. For example, there is no
support for emulating openGl graphics, Near Field Communication (NFC),
or even Wi-Fi. to ensure maximum compatibility, you should always test
your final application on a real hardware device.
Note: For security reasons, the hierarchy Viewer will connect only to
devices running developer versions of android. in practice, you will
be able to use hierarchy Viewer only with the emulator or a phone
that has been hacked to enable root access.
2. Click the Load View Hierarchy button, and you will see something like
Figure 1.16.
tools 27
There are four primary components in the Hierarchy Viewer:
J The left sidebar displays all connected devices, along with the running
processes on each device. This is where you select your application.
J The Tree View displays a graphical representation of your UI layout. You
can see exactly how many components make up your layout. Large layouts
with many nested components will take much longer to draw than simple
layouts. If you look closely, you will see colored circles on some components.
These give you an at-a-glance indication of the time taken to draw the view
and its children. Green is faster, red is slower. You can click a view to get
more information about its draw time, along with a small preview of the
view as it appears onscreen.
J The Tree Overview provides a quick zoomed-out view of the entire hierar-
chy, giving you a quick feel for the complexity of the layout. This pane also
provides quick navigation around the Tree View pane.
J The Layout View shows an outline of the actual displayed application. This helps
to orient the view components to the actual displayed UI. By clicking around in
this pane, you can see which components make up each portion of the display.
If you look closely at the hierarchy displayed for the Hello World application,
you may notice that it contains more components than are listed in the main.xml
file. Here’s a quick explanation:
J The topmost component is the PhoneWindow. This represents the display
of the device. It is always present and is the base container for the entire
display, excluding the notification bar.
J There is a LinearLayout directly below the PhoneWindow. This is not the
LinearLayout in our main.xml. Rather, this layout is drawn by the system
to display the title bar above the content. Notice the extra FrameLayout and
TextView? That is the title bar of the app. If you run the app with no title
bar, then this layout would be removed.
J The other FrameLayout is the application. This layout contains a child
LinearLayout. The child LinearLayout is from the main.xml file in the
example. It contains the two TextViews and the Button you created earlier
in the Hello World app.
Note: android version 4.0 and above has the ability to take
screenshots without using ddms. simply hold the power and volume
down buttons at the same time, and a screenshot will be saved to your
device image gallery.
tools 29
FIGURe 1 .18 An example of the
Draw 9-patch tool. The button
can stretch, but the corners
remain the same.
other tools
In addition to the common Android UI tools, there are some lesser-known tools
that are useful for perfecting your app UI.
drAW9pAtCH
Images used in Android applications are often stretched to fit the available area
on a device. This can distort the image, resulting in ugly graphics. Android uses
an image called a 9-patch to handle scaling without distortion. For example, all
buttons in Android are 9-patch graphics that stretch but maintain proper round-
ing on their corners (Figure 1.18). A 9-patch image is simply a standard image file
with an additional 1-pixel border. By coloring the pixels in this border black, you
can indicate which parts of the image should stretch as the image is scaled up. The
Android SDK provides the draw9patch command-line tool for creating these images.
lAyoUtopt
Optimizing layouts by hand can be a tedious job. The layoutopt tool can do some
of the work for you by analyzing your layouts and displaying inefficiencies in
the view hierarchy. This command-line tool takes a list of XML layout files, or a
directory of files, and outputs the results of its analysis. While this isn’t sufficient
for debugging complex hierarchies, it can help in providing a first pass at fixing
layout slowdowns.
http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html
The Android Asset Studio is part of the Eclipse ADT plugin and can be
accessed by selecting File>new>Android Icon Set.
monKey
When creating applications, it’s important to thoroughly test every aspect of the
user experience. It can be difficult to truly test a UI, because the developer of the
app is familiar with the interface and won’t try to do things that are unexpected
or just plain weird—things like pressing multiple buttons at the same time while
rotating the phone. This is where the Monkey tool comes in. The Monkey runs a
specified number of iterations and randomly hits areas of the screen, changes the
orientation of the device, presses volume and media keys, and generally just does
crazy things. This is often a simple way of rooting out unexpected errors.
tools 31
WraPPiNG up
This chapter introduced the Android framework by having you create the standard
Hello World application and explore the tools available for building user interfaces
on Android. Along the way, you learned that
J The AndroidManifest.xml file declares all the features used by your applica-
tion. Use the manifest to prevent your app from running on unsupported
hardware.
J Images and layouts are separated into folders that allow the Android system
to select the best resources for the device’s current configuration at runtime.
J The Activity class is the primary building block of Android applications.
Understanding its life cycle is key to building great apps.
J The graphical layout editor provides a quick and easy way to create your
applications.
J You should use the Hierarchy Viewer tool to debug performance issues in
your views.
J You can use the DDMS tool to take screenshots of your application.
35
creAting aN App
To get started, create a new project called TimeTracker. This will be your app
project throughout the book. In this chapter, you’ll work through some simple
layouts and build a minimally functional application. Figure 2.1 shows what you’ll
have built by the end of this chapter.
This book won’t cover much of the back-end logic and will instead focus on
the user interface code. However, all the code is available on the book’s website,
www.peachpit.com/androiduifundamentals,for you to download.
Margin
Padding
grAvity
By default, Android will place views on the left side of the screen. To change this,
you use the gravity attribute. The gravity attribute controls the default position
of a view’s children inside that view. For example, you can use the gravity attri-
bute on a linear layout to position its child views on the right side of the screen. By
default, layout containers have gravity set to left. Most other views have their
default gravity set to center.
Note: When setting gravity, you must account for the size of the views.
the gravity attribute only positions child views inside the parent view.
if the parent view takes up half the screen, then gravity will position
children only in that half of the screen. if you are trying to use gravity
and not getting the results you expect, check the size of your views.
more options
There are many more optional view attributes. Some of them are specific to par-
ticular views, like setting the source of an ImageView or the text of a TextView.
Some are available on every view but have a default value, like the background
used for images. Some can even be used to create animations for your views. You
should explore these attributes and get familiar with the basics. You’ll learn about
more attributes throughout this book, but there are too many to cover them all.
The Android view hierarchy starts with a layout container. These containers hold
the child views and arrange them relative to each other. There are several container
types with different characteristics, optimizing them for different situations.
FramelaYout
The simplest layout container is the FrameLayout. This container does not arrange
child views at all. It simply presents each view, one on top of the other. The order of
the views is based on their declaration in the XML file: Views declared later in the
file are drawn on top. Use this layout whenever you want to create overlapping views.
FrameLayout is especially useful when creating customized tappable elements.
You can use the FrameLayout to pair a button with an ImageView, setting the button
background to be transparent. This gives you more control over the padding and
scaling of button images than just setting a background does.
taBlelaYout
The TableLayout displays data in a tabular format (Figure 2.7). It arranges sub-
views into rows and columns, with each row contained in a TableRow container.
A TableLayout will have as many columns as the TableRow with the most cells.
Unlike the children of most views, the children of a TableLayout cannot specify a
ArrAnging vieWs 41
layout_width. This is handled by the TableLayout and will be set for you. Cells can
be marked to span multiple columns and expand or shrink to fill available space.
You should use this layout only when displaying a table of data. In other cases,
use a LinearLayout, a RelativeLayout, or the new GridLayout.
liNearlaYout
You saw the LinearLayout in Chapter 1. You’ll be using this container a lot in your
apps. As the name implies, this container arranges child views in a single direction,
vertically or horizontally. The orientation attribute sets the direction for a linear
layout’s child views. Child views specify how much space they will consume within
the linear layout. They do this by setting a layout_weight. This parameter specifies
the relative weight of one view versus the other views. By default, all views have
a weight of 0. This means they will take up exactly as much space as they need to
contain their content. Setting a weight higher than 0 will make a view expand to
fill the remaining space in the layout. The relative value of the weight versus the
weight of other views will determine how much space a particular view consumes.
The buttons in Figure 2.8 are contained in a linear layout with orientation set
to vertical. Each button takes up as much space as needed to contain its content.
The top button has its weight set to 0 and is taking up only the space needed to
display its content. The other two buttons have their weights set to 1 and 4, so in
addition to their normal size, they expand to fill the remaining space. The bottom
button has a higher weight and consumes more of the available space. It actually
takes four-fifths of the remaining space, leaving one-fifth for the third button
(1 + 4 = 5). Using layout weights allows you to create proportionally arranged views,
greatly increasing the flexibility of your layouts.
ArrAnging vieWs 43
The linear layout is simple to use and perfect for the first version of the
TimeTracker app. Remember Figure 2.1? Here is how you create that UI:
2. Open the res/main.xml file that was created automatically. Replace its
content with the following XML layout:
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:layout_width=”match_parent”
p android:layout_height=”match_parent”
android:orientation=”vertical”>
<TextView android:id=”@+id/counter” android:text=”0”
android:layout_height=”wrap_content”
p android:textAppearance=”?android:
p attr/textAppearanceLarge”
android:gravity=”center” android:padding=”10dp”
android:layout_width=”match_parent”
p android:textSize=”50dp”></TextView>
<LinearLayout android:layout_height=”wrap_content”
android:layout_width=”match_parent”
p android:orientation=”horizontal”>
<Button android:text=”@string/start”
p android:id=”@+id/start_stop”
android:layout_height=”wrap_content”
p android:layout_width=”0dp”
android:layout_weight=”1”></Button>
<Button android:text=”@string/reset”
p android:id=”@+id/reset”
android:layout_height=”wrap_content”
p android:layout_width=”0dp”
This XML code uses a linear layout to arrange three children: a text view to
hold the current time, another linear layout that will hold the two buttons, and
a list view that displays a list of all previous times. The buttons are arranged
using a second linear layout with orientation set to horizontal. Note that
you set the layout_width of both buttons to 0dp and the layout_weight to 1.
This makes the buttons expand to fill the width of the layout and divide that
space equally. The list view will display a list of times with a custom layout for
each row. You’ll learn more about using list views in the next section.
relatiVelaYout
The other common layout container is the RelativeLayout. Relative layouts are
more flexible than linear layouts, but they are also more complex. As its name
implies, the relative layout arranges child views based on their position relative
to the other views and to the relative layout itself. For example, to place a text
view just to the left of a button, you would create the text view with the attribute
toLeftOf=”@id/my_button”. This flexibility allows you to create very complex UIs
within this one container.
ArrAnging vieWs 45
FIGURe 2 .9 A relative layout
with buttons arranged in
corners and center
Note: the child views of a relative layout are arranged in the order they
are declared. so if a view is declared to be in the center of the layout,
all subsequent views aligned to that view would be arranged based on
the center of the view.
attriButes n
layout_alignParentTop, layout_alignParentBottom, These attributes will align the view with the parent. Use
layout_alignParentRight, layout_alignParentLeft these to fix the view to the sides of the RelativeLayout
container. The value can be true or false.
layout_alignTop, layout_alignBottom, These attributes are used to align the view with another
layout_alignRight, layout_alignLeft view. Use these to line up views in the layout. The value
must be the id of another view.
layout_alignBaseline This attribute sets all the edges of a view to align with the
specified view. This is useful when you have overlapping
views and need them to exactly match. The value must be
the id of another view.
layout_above, layout_below, layout_leftOf, Use these attributes to position a view relative to another
layout_rightOf view. This attribute sets rules on the view to ensure that it
never crosses the boundary set by the edge of the target
view. The value must be the id of another view.
It can be tricky to master using the relative layout, but it will pay off when you
create more-complex UIs. Remember that if you find yourself creating multiple
nested linear layouts, you should consider using a relative layout to optimize the
drawing of your UI.
ArrAnging vieWs 47
FIGURe 2 .10 GridLayout pro-
duces complex layouts without
nested containers.
GridlaYout
Android 4 brought a new layout container called GridLayout. As its name implies,
it arranges views into a grid of columns and rows. This layout makes it easier to
create the common “dashboard”-style UI seen in apps like Google+. You would
normally create such a UI using a TableLayout, but GridLayout allows you to
create the same layout with a flatter hierarchy. This improves performance by
reducing the number of views that Android has to draw. GridLayout has also been
designed to support drag-and-drop creation of UIs using the graphical layout edi-
tor. Developers will be able to create complex and efficient layouts just by using a
GridLayout and the layout editor.
Figure 2.10 shows an example layout created using the GridLayout container.
In this layout are four buttons arranged into rows and columns. The XML to cre-
ate that layout is:
ArrAnging vieWs 49
Unlike a TableLayout, a GridLayout does not need explicit TableRow elements.
The buttons themselves declare the rows and columns in which they should appear.
By default, this layout will not include any space between the buttons. To add
space, you can use the traditional margin and padding parameters, or you can
use a new view that was introduced in Android 4: Space. This view simply adds
a gap between the elements of a layout. When using drag and drop to create a
layout in the graphical layout editor, spaces are automatically inserted to achieve
the desired appearance. Here are the Spaces created by the layout editor for the
layout in Figure 2.10:
<Space
android:layout_width=”58dp”
android:layout_height=”1dp”
android:layout_column=”0”
android:layout_gravity=”fill_horizontal”
android:layout_row=”0” />
<Space
android:layout_width=”128dp”
android:layout_height=”1dp”
android:layout_column=”1”
android:layout_gravity=”fill_horizontal”
android:layout_row=”0” />
<Space
android:layout_width=”134dp”
android:layout_height=”1dp”
android:layout_column=”2”
android:layout_gravity=”fill_horizontal”
android:layout_row=”0” />
ArrAnging vieWs 51
displAying a list
One of the most common view types you’ll use to develop an app is the ListView.
This view presents a vertically scrolling list of items. Each row generally holds some
text but will often include other views, such as ImageViews and Buttons (a good
example of this is the Contacts app). Use a ListView whenever you have a list of
data to present to the user. This view is so common that Android actually provides
built-in activities that just display a list.
listaCtiVitY
A ListActivity will bind to a default view containing a ListView. There is no need
to call setContentView in the activity’s onCreate method, because the ListActivity
is already set to a ListView by default (though you can define a custom view if you
choose). The ListActivity class also contains a few convenience methods for
retrieving and setting the list data and for handling item selection. Although it’s
not necessary to use a ListActivity to display a list, you should consider using it
whenever you want to display a list of data to the user.
The ListActivity actually sets its content to a special layout built into the
Android OS. This layout contains a single ListView as its content. There are
other built-in layouts you can use when creating your app, a number of
which are contained in the android.R.layout class. Here are two that you
could use with a ListView:
J android.R.layout.simple_list_item_1 is used to display a single line of
text in a row of a ListView.
J android.R.layout.two_line_list_item displays two lines of text per row
of a list.
In addition to the layout files, Android also has built-in styles, menus, draw-
ables, and other useful views. You should explore the Android package for
useful defaults for your app.
<ListView android:layout_width=”match_parent”
android:layout_height=”match_parent” android:id=”@+id/list”>
</ListView>
List views have a few special attributes that you can use for more complex
layouts. The first is the android:entries attribute. Use this attribute when you
have a static, unchanging list of values to populate the list view. Using the entries
attribute, you can simply reference a resource and not have to programmatically
populate the list. Attributes for altering the appearance and behavior of the divid-
ers between rows are also available. In general, you should stick with the defaults
and not deviate from the platform look and feel.
roW lAyoUt
Creating the layout for a list row is the same as for an activty: You create an XML
file with a layout container and several views. Each row will contain that layout,
allowing you to set values for text and images. The Android platform provides
several default row layouts. These are generally sufficient for the list views that
you will create. However, you can also create custom layouts for the rows of the
list. To create a custom layout, you simply create a new layout file and use it when
binding data to the list view.
The time-tracking app will require a custom layout for its list view. In the res/
folder, create a new layout file called time_row.xml:
displAying A list 53
android:gravity=”center” android:paddingLeft=”10dp”
android:paddingRight=”10dp” android:paddingBottom=”20dp”
android:paddingTop=”20dp”>
<TextView android:id=”@+id/lap_name”
p android:layout_height=”wrap_content”
android:text=”Lap 1” android:layout_weight=”1”
p android:layout_width=”0dp” />
<TextView android:id=”@+id/lap_time”
p android:layout_height=”wrap_content”
android:text=”00:00:00” android:layout_weight=”1”
android:layout_width=”0dp” android:gravity=”right” />
</LinearLayout>
This file uses a simple linear layout to display two text views side by side: one
for the name of the session and one for the time. The linear layout uses padding
to create some space between the text views.
displAying A list 55
This method inflates a custom layout for each row of the list view. Inflating a
layout is the process of converting XML layouts into a set of View objects (you’ll
learn more about this in Chapter 3). As the user scrolls through the list, the system
will call this method to create the rows of the list. Rows that are no longer visible
will be garbage collected. You should take care to prevent needless allocation of
memory in data adapters. Unnecessary garbage collection events are one of the
primary causes of stutter in Android animations. You will learn some tricks for
creating efficient data adapters later in this book.
tip: don’t inflate new views unless you need to. in this code,
the view is only inflated if it doesn’t already exist. this is an opti-
mization that prevents unnecessary object creation and garbage collection.
loaders
Loading data into a list adapter can be a tedious process: You need to handle things
asynchronously to avoid performing too much work on the main thread; you
need to keep the displayed data fresh by reloading the list adapter when the data
changes; and you have to maintain the data across orientation changes, which
destroy and re-create the activity. To simplify this process, Android 3 introduced
a helper class called Loader. The Loader class takes a lot of the drudgery out of
loading data asynchronously.
The Loader class is available to all versions of Android through the compatibility
package. This package contains implementations of new Android APIs, like loaders
and fragments, allowing you to use them in older versions of Android. You’ll learn
more about loaders and fragments in a later chapter, but for now just remember
that you can simplify the binding of data to views by using the Loader class.
Android activities represent the interface to the user. All interaction with users
takes place through activities. As a developer, it’s important that you create a fast
and responsive application that puts the user first. This can be as simple as using
easy-to-read text views or as complex as saving the input to a data field as soon
as the user types a query. Understanding activities is key to creating responsive
and usable applications.
deClariNG aCtiVities
All activities must be declared in your application’s manifest file. Failing to do so
will result in your app throwing an exception when it first runs. Here is a sample
activity manifest entry for the TimeTracker app:
<activity android:name=”.TimeTrackerActivity”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
The manifest entry contains basic information about the activity, such as its
class name and user label. Notice the “.” in the name attribute? That’s a shortcut
for using the full package name listed in the <application> element. This activity
also declares an intent filter that is used to respond to intents sent by the system.
In this case, it declares that this is the main activity for the app and that it should
respond to the android.intent.category.LAUNCHER intent, which is sent when
an app icon is tapped on the home screen. You should declare intents that launch
activities in the manifest file.
UnderstAnding ACtivities 57
uNderstaNdiNG the aCtiVitY liFe CYCle
Activities are short lived—they are continually being created and destroyed. It’s
up to the developer to properly handle these transitions as the user navigates an
app. You create an activity by extending the Activity class and implementing a
series of callbacks that the system calls when your activity transitions between
states. Activities have three basic states, listed in Table 2.2.
state n
resumed or running In this state, the activity is focused and visible to the user.
Users interact with your activity while it is in this state.
Stopped Your activity is placed in this state when the user transitions
to a new activity and your activity is no longer visible. The
system will often destroy your activity to reclaim resources
when it is in this state. If all activities of an app are stopped,
the system will kill the entire app process to reclaim resources.
tip: the onPause method is where you should save any data the
user would expect to keep. For example, if you created an email
application, any text entered by the user should be saved to your database
during the onPause callback.
onCreate()
Activity is Activity
running comes to the
Process
is killed foreground
Another activity
comes to the front Activity
comes to the
Other foreground
applications onPause()
need memory
Activity is no
longer visible
onStop()
onDestroy()
Activity is
shut down
You must always call the superclass implementation of activity callbacks that
you implement. If you fail to do so, your activity will throw an exception.
As an activity transitions between the three states, the callbacks of the Activity
class are triggered. Figure 2.11 gives an overview of the callbacks and when they are
triggered. There are two important things to remember about activities: The system
will aggressively destroy your activity when it’s not visible to the user; and the call-
backs all run on the main thread, so you should not perform any long-running or
computationally expensive operations in those callbacks.
UnderstAnding ACtivities 59
The TimeTracker app will need to override the onCreate method for now. You’ll
also override onDestroy when you create the timer logic. Later, when you imple-
ment the database, you will want to override the onPause method to save any data
the user has entered.
2. Override the onCreate method and set up the views using the following code:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize the timer
TextView counter = (TextView) findViewById(R.id.counter);
counter.setText(DateUtils.formatElapsedTime(0));
if (mTimeListAdapter == null)
mTimeListAdapter = new TimeListAdapter(this, 0);
ListView list = (ListView) findViewById(R.id.time_list);
list.setAdapter(mTimeListAdapter);
}
}
This code sets the XML layout file for your activity by calling setContentView.
The setContentView method inflates the XML layout and adds it to the activity view
hierarchy. Next, the findViewById method retrieves a reference to the TextView
that will hold the current time. It sets the default time value using a DateUtils
format. Finally, the ListAdapter that you’ll use to load data into the ListView is
created and set on the ListView instance.
You should call setContentView in your activity’s onCreate method. You may
call it as many times as you want in onCreate, but only the last call will execute.
Once the view hierarchy has been loaded, you cannot call setContentView again.
However, you are still free to update the layout using Java APIs.
uNderstaNdiNG tasks aNd the BaCk staCk FIGURe 2 .12 The back stack.
Pressing the Back button will
Android applications are typically constructed using a series of activities. The pop the most recent activity
system groups these activities into tasks. Each task represents a set of activities off the stack.
as a stack, with activities being pushed onto the stack when the user navigates
away from them and being popped off the stack when the user navigates back to
them (Figure 2.12). This is called the back stack. New tasks are created when the
user opens a new activity that is not associated with the current activity. Every
task has its own back stack.
The main, or UI, thread for an Android app is where all UI events are trig-
gered. Every button you press generates an event that is dispatched via the
main thread. For this reason, it is very important to use worker threads for
handling long-running operations. However, updates to the UI are not thread
safe. If you try to update the UI from one of your worker threads, an excep-
tion will result. Android provides a number of APIs for dealing with this:
J The Activity.runOnUiThread method
J Message handlers
If you need to update the UI of your application, make sure you either do
it from the UI thread or use one of these APIs. You’ll learn more about the
AsyncTask class later in this chapter.
UnderstAnding ACtivities 61
Open
Tap Tap New Tap
Activity Item Home App Button
Task 1 Task 2
Activity 2 Activity 2
Task 2 Task 2
Task 2 New task Activity 1 Activity 2
back stack created
Activity 1
FIGURe 2 .13 Two tasks and A simple example will demonstrate (Figure 2.13).
their back stacks
1 . A user opens an application. This creates a new task. The example is a
ListView.
3 . The user presses Home, then opens a new app. This creates a second task,
containing the main activity of the new app.
4 . The user navigates to a new activity in this task, again by pressing a list item.
There are now two tasks and two back stacks. The user can switch between
the two tasks by pressing Home and tapping one of the application launchers.
Alternatively, on Android 4.0 and later, users can press the task switcher button
to switch tasks. The Back button will act on the active stack and pop the topmost
activity from the task the user is viewing.
@Override
protected void onSaveInstanceState(Bundle outState) {
ListView list = (ListView) findViewById(R.id.time_list);
int pos = list.getFirstVisiblePosition();
outState.putInt(“first_position”, pos);
super.onSaveInstanceState(outState);
}
When onCreate is called, retrieve the list position from the input Bundle and
re-scroll the list. Handling details like this makes your app user friendly.
UnderstAnding ACtivities 63
PreVeNtiNG Anrs
An Android application runs in its own process, which is sandboxed from all other
applications. The application is run by a single thread: the main, or UI, thread. To
keep the app responsive, Android limits the time that any function call may take. If
the function exceeds this time limit, an Application Not Responding (ANR) dialog
will be shown to the user, asking them to either wait or force the app to close. You
want to avoid causing an ANR at all costs. ANRs happen when you perform long-
running operations on the main thread; examples include network I/O, disk I/O,
database queries, and CPU-intensive calculations.
striCtmode
Android 2.3 introduced a new developer tool called StrictMode. This tool will detect
disk or network operations occurring on the main thread and take action to warn
the developer. It provides a number of methods for warning the developer, from
simple logging statements to full-blown application crashes.
StrictMode is not guaranteed to find all disk and network I/O occurring on the
main thread. In particular, any accesses occurring through a Java Native Interface
(JNI) will not be detected. Be aware that although StrictMode is helpful, it is not
sufficient for creating responsive applications.
deClAring striCtmode
Here is a simple StrictMode declaration that detects all types of network and
disk I/O:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDialog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
It’s recommended that you enable StrictMode in all projects you create. It’s
better to catch these cases earlier in development, when significant architecture
changes will not be required.
disABling striCtmode
While StrictMode is very helpful for creating responsive applications, you should
disable it when you release your app in the market. Otherwise, users may encoun-
ter the policy violation dialogs or even experience app crashes. A simple method
of handling this is to enable StrictMode only if the app is built in debug mode
(signed with a debug key). To detect if an app is running in debug mode, check the
ApplicationInfo flags. The following code snippet checks if your app was built
using the debug signing key:
preventing Anrs 65
BaCkGrouNd tasks
A common situation that you’ll encounter is the need to perform some long-running
operation that can’t be done on the UI thread—things like downloading RSS feeds,
writing a file, or running a timer. These tasks could potentially take many seconds
to run and would block the UI thread from updating. There are several strategies
you can take to handle these situations. Typically, you will create a new thread that
can perform the task, updating the UI or application state when finished. Here are
some strategies for implementing this behavior.
This code updates two fields of the enclosing Activity class that keep the
current time. Then it updates the UI (remember that the Handler callback will
run on the UI thread by default unless you explicitly give it another thread to run
on). Finally, it schedules another message for 100 milliseconds in the future. The
sendEmptyMessage method also takes an integer parameter that distinguishes it.
Here, there is only a single message, so set it to 0. Using the handler messaging
API, you can create convenience methods for using the timer:
preventing Anrs 67
2. The stopTimer method just removes any messages from the handler mes-
sage queue.
private void stopTimer() {
mHandler.removeMessages(0);
}
3. The resetTimer method will call stopTimer and then add the current time
to the list adapter, which will display it in the list.
private void resetTimer() {
stopTimer();
if (mTimeListAdapter != null)
mTimeListAdapter.add(mTime/1000);
mTime = 0;
}
You now have all the logic for the timer completed.
Activity.runOnUIThread
It’s very common to use a handler just to update the UI from a back-
ground thread. Android provides a shortcut for these situations with the
Activity.runOnUiThread method. This method takes a runnable and posts
it to the UI thread message handler. When available, the main thread will
then run the code contained in that runnable.
preventing Anrs 69
The three type arguments given to the task are used to specify the type of the
parameters given at execution time, the type of the parameters given to set prog-
ress, and the result type returned when the background task has completed. You
can use onPreExecute to update the UI before your task runs, onProgressUpdate
to update a UI progress indicator, and onPostExecute to update the UI when the
task finishes. These methods all run on the main UI thread, so there is no danger
in updating your views. All code runs in the doInBackground method, which you
can think of as just being the run method of a thread.
AsyncTask is most useful for quick one-off tasks that need to update a UI com-
ponent (such as downloading new posts from Twitter and then loading those posts
into a timeline).
You almost have everything completed for the first version of the TimeTracker app.
It just needs some logic to handle the button presses.
This sets the TimeTrackerActivity class to be the listener for Button events.
You’ll learn more about event handling in the next chapter, but for now, update
the TimeTrackerActivity to implement the OnClickListener interface.
2. Override the onClick method. This method will be called each time one of
the buttons is pressed.
@Override
public void onClick(View v) {
TextView ssButton = (TextView) findViewById
p (R.id.start_stop);
3. Check which button was pressed. If the user pressed the Start/Stop button,
check the state of the timer. If it’s stopped, you need to start it and change
the button text to “Stop”; otherwise, stop the timer and change the button
text to “Start”:
if (v.getId() == R.id.start_stop) {
if (isTimerStopped()) {
startTimer();
ssButton.setText(R.string.stop);
} else {
stopTimer();
ssButton.setText(R.string.start);
}
4. If the user pressed the Reset button, reset the timer and counter TextView
and then set the Start/Stop button text to “Start”:
} else if (v.getId() == R.id.reset) {
resetTimer();
TextView counter = (TextView) findViewById
p (R.id.counter);
counter.setText(DateUtils.formatElapsedTime(0));
ssButton.setText(R.string.start);
}
}
You can now run the app! It should look like Figure 2.14.
You should be able to start and pause the timer and record the previous values
in the list. In the next chapter, you’ll go further—you’ll extend the app to work
on multiple screen sizes, add some notifications, and make the timer work in the
background.
This chapter introduced the common Android views and layout containers, along
with the attributes used to display them. You also started building a basic time-
tracking app. Along the way, you learned that
J Android provides many basic form widgets for building your UI.
J There are several layout container types, and they each have specific situ-
ations in which you should use them.
J Any updates to your app UI must take place on the UI thread.
J You can use a ListView to display lists of data to your users, and you can
bind data to that ListView by using a ListAdapter.
J Understanding the activity life cycle is fundamental to building a respon-
sive app.
J You can use StrictMode to prevent Application Not Responding (ANR) errors.
WrApping Up 73
3
GOInG FurtHer
Now that you have a basic app, it’s time to add some
more features. To start, you’ll need to handle some
more events from the user, create an ongoing notification,
and list extra options in a menu. Along the way, you’ll learn the
specifics of supporting multiple device configurations; explore
event callbacks and multiple event filtering; create notifications,
toasts, and dialogs to alert the user; and learn when and how to
create menus.
75
suPPortiNG multiple
sCreeN sizes
resourCe QualiFiers
Android has many features that are inspired by the web (not surprising given that
it was created by Google). Nowhere is this more apparent than in the design phi-
losophy of Android views. In contrast to iOS devices, Android apps don’t know
the screen resolution, size, or aspect ratio of the devices they run on, but you can
use the View classes to stretch and shrink layouts to fill the available space, just
as you can on the web.
You’ve already seen how to create Android layouts, and you’ve learned how to
create a layout that stretches to fill the available space. This creates a flexible layout,
but it’s usually not enough to make your app work on every device configuration.
Often, you need to do more than just adjust the size of elements—you actually
need to create a different layout to provide a useful interface. To make this easier,
Android uses a series of layout qualifiers that define different device configurations.
The layout qualifiers are appended to the resource folder names. Using these fold-
ers, you can create a layout for a specific set of device configurations; Android will
automatically select the appropriate layout file for the user’s device.
For example, when a phone is held in portrait, the basic XML layout file for
that device will be loaded and displayed. When a phone is rotated to landscape, a
landscape-specific layout can be loaded, but only if it is available; if no landscape
version is available, the standard portrait version will be loaded. Layout qualifiers
exist for screen density, orientation, screen size, mobile country codes, region,
platform version, primary navigation mode, and much more. Table 3.1 summarizes
the important qualifiers.
1. In the res/ directory, create a new folder named layout-land/ and put a
copy of the main.xml file into it.
2. Open the new file and change the string to “Hello Landscape”.
Now when you start the app, you will see the standard layout in portrait but
the new layout in landscape.
n examPle n
Screen orientation port The orientation of the device. This changes often while your
land application is running.
Screen pixel density ldpi The number of pixels per square inch of the display. An image
mdpi will appear as different physical sizes depending on the pixel
hdpi density of the screen. Use these qualifiers to create images of
xhdpi different sizes for each screen density (use nodpi to provide image
nodpi resources that you do not want to scale based on the screen
density). Most Android devices on the market now are mdpi and
hdpi devices. xhdpi was introduced in API level 13 and is intended
for tablet devices.
Screen size small A rough approximation of the physical screen size. Most Android
normal devices are normal to large. The xlarge qualifier represents tablet
large devices. consider using smallestWidth, available width, and avail-
xlarge able height for Android 3.2 and above.
smallestWidth, available width, sw320dp The available screen pixels for width and height. These qualifiers
available height sw720dp were added by Android 3.2 and make it easier to create layouts for
w720dp specific screen sizes.
h720dp
API version v6 The minimum API version supported by the device. For example,
v14 the qualifier v7 means these resources should be used for all
devices running Android API version 7 and later.
note that this is not an exhaustive list of qualifiers. consult the Android documentation for all the available options.
3 . If any folders match this qualifier, eliminate all folders that do not match.
If no folders match this qualifier, return to step 2.
The exception to these rules is screen pixel density. Android will scale any
resources to fit the screen; therefore, all pixel densities are valid. Android will
select the closest density that is available, preferring to scale down larger densities.
/res/layout/
/res/layout-notouch/
/res/layout-land/
Android will run through its steps to select the best resource:
2 . There are no folders with screen-size qualifiers, so skip to the next qualifier.
4 . The next qualifier is pixel density. There is no exact match, so continue to the
next qualifier. If no other qualifiers match, select the nearest pixel density.
5 . The last qualifier is touchscreen type. In this case, finger means that the
device has a capacitive touchscreen, so eliminate all folders that do not
contain the finger qualifier.
Android performs this procedure for every resource your layouts require. Often,
resources will be mixed from multiple locations. For example, the layout may be
taken from the /res/layout-ldpi/ folder, but the drawable resources could be
taken from the /res/layout-hdpi/ folder. Remember that Android selects each
resource independently and will pick the best match. If you start getting strange
problems with your layouts, check the precedence on your resource folders. Android
may be loading different resources than you expect!
Note: android will select only screen size resource qualifiers that
are smaller than or equal to the device configuration (excluding
the pixel density qualifier). if you have only xlarge resources and
the device has a small screen, then your app will crash when it runs.
deNsitY-iNdePeNdeNt Pixels
Using resource qualifiers and Android’s layout containers will let you create lay-
outs that stretch and compress to fill available space. But sometimes you need to
specify the exact dimensions of a view. If you’ve done GUI programming, you’re
probably used to specifying exact sizes in pixels. Android supports this, but you
should avoid using absolute pixel or dimension values when you create your app.
The pixel density of devices varies greatly, and the same resource will appear as
a different physical size on each device. Figure 3.1 shows an example of a button
that has its height and width values specified in pixels. At each screen density, the
relative size of the view is different.
To handle this, Android has several ways of declaring dimensions in a density-
independent manner, summarized in Table 3.2.
Figure 3.2 shows the previous example, but with the height and width of the but-
ton specified in dp. The buttons appear much closer to the same size on the screen.
In general, you should use dp for all units of measure (or sp for text sizes).
Using these units will make your layouts appear consistent across device sizes and
densities. This will ensure you get maximum device compatibility and will help
you avoid layout headaches later.
uNit n
px The physical pixels on the device. This will make a view take up an exact
number of pixels on the screen. However, since every device has a dif-
ferent number of pixels, and the pixels might be different physical sizes,
this unit should be avoided.
in Inches on the screen. This will make a view take up an exact number
of inches on the screen. Again, since every device has a different screen
size, this unit should be avoided.
mm Millimeters on the screen. This will make a view take up an exact num-
ber of millimeters on the screen. Like inches, this unit should be avoided.
pt Points, which are 1/72 of the physical screen size. Much like in and mm,
points are based on the physical size of the device and generally are
not used.
sp Scaled pixel. This is the same as dp, but it is scaled based on the user’s
font size preference. You should use this unit when specifying font or
text sizes that need to be adjusted based on the user’s preferences.
9-PatCh GraPhiCs
Using the resource folders and density-independent pixels will get you most of
the way to a flexible layout, and Android will scale your images appropriately in
most situations. But often you will need to create image resources with rounded
corners. These images won’t stretch properly and will appear distorted. To handle
that case, Android supports a feature known as a 9-patch graphic. This is simply
a PNG file with a 1-pixel border around it. The Draw 9-Patch tool (see Chapter 1)
provides an easy way to create 9-patch graphics (Figure 3.3). These images can be
stretched in the areas indicated by the shaded region (marked with black pixels
in the border of the image). By using the Draw 9-Patch tool, you ensure that your
image will stretch but will maintain the proper rounding on corners. All of the
stock Android button resources use 9-patch graphics.
creating flexible layouts may seem arduous at first, but it will soon become
second nature. remember these points, and you should be able to build an
app that is compatible with the majority of Android devices:
J Always use density-independent pixel values in your layout.
Android is designed to run on portable devices that are carried everywhere and
used in sporadic bursts. To ensure that users get the full benefit of these devices,
Android supplies a robust set of notification techniques to ensure that users are
immediately aware of any events. This section covers the notification options in
order of increasing interruption to the user.
toasts
A toast is the most basic and least intrusive notification. This is a simple message
that is flashed on the screen for a short time (typically 5 to 10 seconds). It’s intended
to give the user immediate feedback on some event that is relevant to their cur-
rent situation. For example, if a social networking app is attempting to update a
user’s status, it could use a toast to inform the user when the status is successfully
updated. Figure 3.4 shows an example toast.
Toasts include options for setting their position on the screen. Use the
setGravity method to change the default display location. And as with most views
in Android, you can override the default layout of a toast and create a custom toast.
Use toasts when you want to give quick feedback to the user but don’t expect
them to take any action.
HAndling notiFiCAtions 85
FIGURe 3 .5 One-time and
ongoing notifications in the
Android notification tray
A notification is created with an icon, ticker text, and timestamp. This example
creates a notification with the resource located at /res/drawable/icon, the ticker
text set to the string “Hello World!”, and the time set to the current time.
Once you have created a notification, use the NotificationManager to display
that notification to the user:
NotificationManager nm = (NotificationManager)
p getSystemService(NOTIFICATION_SERVICE);
Context context = getApplicationContext();
CharSequence message = “Hello World!”;
Intent intent = new Intent(this, Example.class);
String title = “Hello World!”;
String message = “This is a message.”;
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
p intent, 0);
notification.setLatestEventInfo(context, title, message,
p pendingIntent);
nm.notify(ID, notification);
HAndling notiFiCAtions 87
more notiFicAtion options
You can do more than just update the status bar with notifications—
you have the option of playing a sound, flashing an LED, or vibrating
the device when the notification is triggered. You do this by using the
notification.defaults option. For example, to play the default notification
sound, set the defaults option to:
notification.defaults |= Notification.DEFAULT_SOUND;
This will play the user-configured notification sound when your notification
is displayed. You can also use a custom sound for the sound option:
notification.sound = Uri.parse(“file:///sdcard/mysound.mp3”);
There are custom options for LED flashing and vibrations as well. check out the
Notification class in the Android documentation for all options available. Keep
in mind that not all devices will have LED indicators or vibration capability.
dialoGs
Notifications are great for most events, because they don’t interrupt the user. How-
ever, sometimes you need to inform the user of something immediately—perhaps
you need to alert the user to some failure in your app or confirm that they want to
perform an action. To do this, you use a dialog.
Dialogs are small windows displayed over your application (Figure 3.6). They
block all user input into your app and must be dismissed before the user can con-
tinue using your app. This makes them the most intrusive of all notification types.
Android provides several types of dialogs, with different use cases: an AlertDialog
with simple Accept and Cancel buttons, a ProgressDialog for displaying long-
running progress, and date- and time-picker dialogs for accepting user input.
To create a dialog, extend the DialogFragment class and implement either
the onCreateView or onCreateDialog method. Use onCreateView to set the view
hierarchy (what is displayed inside it) for the dialog; use onCreateDialog to create
a completely custom dialog. A typical scenario is to override onCreateDialog and
return an AlertDialog. An AlertDialog is a dialog with some text and one, two,
or three buttons.
HAndling notiFiCAtions 89
Pre-3.0 diAlogs
Dialogs have traditionally been managed as part of the activity life cycle.
However, Android 3.0 introduced a new method of creating dialogs: the
DialogFragment class. This class uses the new Fragments framework to create
and manage the life cycle of dialogs. This is the primary method of creating
a dialog for future versions of Android. The DialogFragment class is available
to previous versions of Android through the compatibility library, a collection
of classes that allows you to use the new fragments APIs on older versions of
Android. You should use DialogFragments for all of your applications.
If you run the app now, the dialog should appear immediately. Dismiss it by
pressing any button. Fragments let you decompose your application into reusable
components, such as this dialog. You’ll learn more about fragments in Chapter 5.
HAndling notiFiCAtions 91
haNdliNG eVents
Like most GUI frameworks, Android uses an event-based model to handle user
interaction. When the user taps the screen, a touch event is fired and the cor-
responding onTouch method of the tapped view is called. By extending the views
in your UI, you can receive these events and use them to build gestures into your
app; similar methods exist for handling focus and key change events.
These event callbacks form the basis of Android event handling. However,
extending every view in your UI is not practical. Further, the low-level nature of the
events requires work to implement simple interactions. For this reason, Android
has a number of convenience methods for registering listeners on the existing View
class. These listeners provide callback interfaces that will be called when common
interactions, like tapping the screen, are triggered. To handle these common events,
you register an event listener on a view. The listener will be called when the event
occurs on that view. You generally want to register the listener on the specific view
the user will interact with. For example, if you have a LinearLayout container with
three buttons inside, you would register the listeners on the buttons rather than
the container object. You have already seen an example of this with the onClick
method in the TimeTracker app.
Note: android event callbacks are made by the main thread (also called
the ui thread). it’s important that you not block this thread, or you
will trigger an application Not responding (aNr) error. make sure to
perform any potentially long-running operations on a separate thread.
AlertDialog.Builder(getActivity())
.setTitle(R.string.confirm_clear_all_title)
.setMessage(R.string.confirm_clear_all_message)
.setPositiveButton(R.string.ok,
p new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mAdapter.clear();
}
})
.setNegativeButton(R.string.cancel, null)
.create();
This code creates a confirmation dialog. The positive button uses a click listener
that will dismiss the dialog and clear the list adapter. The negative button again
just clears the dialog.
HAndling events 93
loNG Presses
A long press is triggered when the user taps and holds on the screen. It is used to
create an alternate action for a view. This can be used to create a context-specific
menu, trigger an alternate action, or drag an icon on the screen. You can think of
the long press as analogous to the right-click on a traditional desktop application.
Long presses are handled by registering an onLongPressListener. Other than
the name, the setup of a long-press listener is exactly the same as a standard click
listener. Here is a simple example of a long press listener that displays a toast message:
The most common usage of long pressing in a UI is for creating a context menu.
For those cases, you don’t create a long press listener but instead create a context-
menu listener. You’ll learn more about context menus in the next section.
HAndling events 95
CreatiNG menus
FIGURe 3 .8 A menu on
Android 2.3
All Android devices before version 3.0 include a menu button. This menu button
creates an activity-specific menu that you can use to provide extra functionality in
your app (Figure 3.8). This frees you to design your UI with only the most important
actions and to hide optional functionality. On Android versions 3.0 and later, the
menu button is generally part of the application UI; it appears as a button in the
action bar. You will learn more about the action bar in Chapter 6.
tip: You should take care not to provide too much functionality
via the options menu. Common user actions should be available
with a single touch in your ui. users may not even know that an action is
available if it is buried in a menu.
meNu laYout
Like all other Android layouts, menus can be defined using XML or Java code.
It’s generally a better idea to use XML, because you can quickly create the menu
options and their order without any boilerplate code.
Add a menu to the TimeTracker app to clear the current list of times:
The basic structure of the menu layout is quite simple: A top-level menu element
contains the item elements; each item element defines a single menu option. The
android:id attribute is required for each item and is how you will reference the
options in your code. The android:title attribute provides the string resource
name that will appear in your app. Although a title is not required, you should
always provide one; otherwise, your menu option will appear as a blank space.
You can optionally assign to your menu items an icon that will be displayed
alongside the text. Icons can help the user quickly understand the available options
in your menu. Menus can be nested inside items, creating submenus. Figure 3.9
shows an example of a menu leading to a submenu.
CreAting menUs 97
There are many more options available for menu items. You should explore
the range of options available and take advantage of menus in your application.
meNu CallBaCks
To provide an options menu in your activity, you need to override the callback meth-
ods onCreateOptionsMenu and onOptionsItemSelected. The onCreateOptionsMenu
callback is called when the user presses the menu button; this is where you create
the menu by using the layout resource file. To do this, inflate the layout file by
using the MenuInflater class. The following code snippet provides an example:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
laYout inFlAtion
The process of converting a layout XML file into a hierarchy of views is called
inflating. This is typically done using the LayoutInflater class, although the
MenuInflater is used for inflating menu layouts. Inflating a view is optimized
by the Android resource compiler and requires a compiled XML file. You can’t
inflate a generic XML file, only those contained in the R.java file.
You can inflate a view by using View.inflate method or by calling inflate
on the LayoutInflator system service. You can retrieve a reference to
the LayoutInflator by calling getSystemService and passing it the
Context.LAYOUT_INFLATER_SERVICE string.
You only need to inflate views that are added to your layout at runtime. call-
ing setContentView will inflate the views in your layout for you. When using
findViewById to retrieve a view, the result is already inflated.
2. Move the dialog creation code from the onCreate method. The result is a
dialog confirming that the user wants to clear all the tasks:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.clear_all:
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(“dialog”) == null) {
ConfirmClearDialogFragment frag =
p ConfirmClearDialogFragment.
p newInstance(mTimeListAdapter);
frag.show(fm, “dialog”);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
CreAting menUs 99
FIGURe 3 .10 Context menus
are opened when the user
long-presses a view.
The method returns true when it has finished creating the dialog to indicate that
the event has been handled and should not be propagated to any other handlers.
CoNtext meNus
A context menu is like a right-click menu in a standard desktop computing envi-
ronment. Context menus work the same way as options menus but are triggered
when the user long-presses a view. To create a context menu, you set the context
menu listener for a view and implement the onCreateContextMenu method. In this
method, you inflate the context menu to display to the user. Finally, you imple-
ment the onContextItemSelected method to handle user selections. You register
the context menu listener using either View.setOnCreateContextMenuListener
or Activity.registerForContextMenu. Figure 3.10 shows an example context
menu. Here is the code to create that menu:
Now that you’ve added a notification, a menu, and a dialog to the TimeTracker
app, you should have something that looks like Figure 3.11. It still runs only while
the app is in the foreground, though. To fix this, you’ll need to create a service
to handle running the timer and updating the notification. A service is how you
perform background tasks on Android. Its life cycle is similar to that of the activity,
but it does not have a UI component. Anytime you need to execute code when the
user is not actively using your app, you should create a service.
Note: the service life cycle callbacks are run by the android main thread.
Just as with activities, you should avoid performing long-running
operations in those methods. instead, start a thread or use message
handlers with background workers to perform the actual background work.
1. Create a new TimerService class that extends Service. Move all the Handler
code from the TimeTrackerActivity to the TimerService, and add some
convenience methods for stopping and resetting the timer:
You still need to update the activity, though, so the service will need to notify
the activity of the current time via the updateTime method.
3. Create a timerStopped method to notify the activity that the timer has
finished:
private void timerStopped(long time) {
// Broadcast timer stopped
Intent intent = new Intent(TimeTrackerActivity.
p ACTION_TIMER_FINISHED);
intent.putExtra(“time”, time);
sendBroadcast(intent);
}
Now when the service updates the time, the activity will be notified and
can update its counter. This will also come in handy later when you create
a widget.
5. Add the notification code to the service, and call it when the timer is updated:
private Notification mNotification;
private void updateNotification(long time) {
String title = getResources().getString
p (R.string.running_timer_notification_title);
String message = DateUtils.formatElapsedTime(time/1000);
Context context = getApplicationContext();
Intent intent = new Intent(context,
p TimeTrackerActivity.class);
PendingIntent pendingIntent =
p PendingIntent.getActivity(context, 0, intent, 0);
mNotification.setLatestEventInfo(context, title, message,
p pendingIntent);
mNM.notify(TIMER_NOTIFICATION, mNotification);
}
You should now be able to run the timer in the background (Figure 3.12).
WraPPiNG up
This chapter introduced basic Android UI concepts for supporting multiple device
configurations, notifications, and options menus. Along the way, you learned that
J Android uses a combination of folder naming conventions, image scaling,
and density-independent dimensions to create flexible layouts for different
device configurations.
J Touch, focus, and key events are available, but you’ll probably want to use an
event listener to handle common user actions such as tapping on the screen.
J Notifications are the primary method of notifying your users, but dialogs
and toasts can be used when you need more or less urgency.
J Menus allow you to add functionality to your app without cluttering the
layout, but you should take care not to hide essential actions from the user.
WrApping Up 107
This page intentionally left blank
Part 2
THE View
FrAmework
4
BASIc Views
The most basic element of Android user interfaces is
the View class. A view represents an area of the screen.
Buttons, lists, webpages, and even empty spaces are rep-
resented by views. Android contains a rich array of pre-built
View classes that provide much of the functionality you will need.
When the built-in views aren’t enough, it’s possible to create spe-
cial views that are just right for your application. In this chapter,
you will learn about the basic view types you can use to build your
layout, discover how to load and display images, and explore the
more advanced views available in Android: MapView and WebView.
111
creAting a BasiC Form
The TimeTracker app looks pretty good so far, but it’s time to add more than just
a list of times. In this chapter, you’ll add some text entry forms and split the app
into multiple activities. When you’re finished, you’ll have something that looks
like Figure 4.1. This section will cover the basic widgets you see in the image, as
well as how to arrange them.
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical” >
<TextView
android:id=”@+id/counter”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
This XML layout keeps the counter and the Start/Stop button from Chapter 2, but
the task list is replaced with the new task detail fields. Note the use of layout_weight
on the description to fill the entire display.
The inputType attribute of your EditText class is a simple bit mask that defines
the type of data you expect the user to enter. The system can then display an appro-
priate keyboard type. You can combine EditText flags (attributes) so that the system
creates a targeted input keyboard. For example, the following EditText attributes
will make the keyboard a number pad for easy entry of phone numbers (Figure 4.3):
<EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:inputType=”phone” />
Along with changing the keyboard, you can use inputType to change the behavior
of the EditText; for example, use flags like textCapSentences and textAutoCorrect
to add capitalization and autocorrection to what the user types. In addition to con-
figuring the input options, you can use an IME option to set the text for the Enter
button, which appears in the lower-right corner of the stock Android keyboard: Use
the imeOptions attribute to select actionGo, actionSearch, actionSend, actionNext,
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical” >
<EditText
android:id=”@+id/task_name”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”@string/task_name”
android:layout_margin=”10dp”
android:textSize=”24dp” >
</EditText>
<EditText
android:id=”@+id/description”
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:layout_weight=”1”
android:layout_margin=”10dp”
android:hint=”@string/description”
android:gravity=”top|left” />
<DatePicker
android:id=”@+id/datePicker1”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
Here you’re using the android:hint attribute rather than android:text. This
displays the desired preset text but removes it as soon as the user starts typing a
value into the field. This edit_task.xml layout also uses the DatePicker view to
make date entry easier.
ButtoNs
You’ve already used buttons to build the current TimeTracker UI. Buttons are simply
TextViews that have a special background image—this background is actually an
XML file that lists the images that should be used for the different button states
(normal, hovered, focused, and pressed). This type of XML resource is called a state
list resource, and you’ll learn more about creating it later in this chapter.
FIGURe 4 .5 A spinner on
Android 4.0
BooleaN ButtoNs
Buttons are convenient for indicating on/off states. Android has a number of views,
including toggle buttons, checkboxes, and radio buttons, that subclass the Button
class and present a toggle between a true value and a false value. In addition,
Android 4.0 introduced an option called the switch. Figure 4.4 shows all these
options for the 4.0 release of Android.
sPiNNers
A spinner looks like a button and displays a list of choices when pressed. Figure 4.5
shows an example of a spinner choice list. The options presented by a spinner
can be specified using the XML android:entries attribute, or you can use a data
adapter to load entries programmatically (you’ll learn more about loading entries
into views via data adapters in Chapter 6).
Often, you will want to give users the ability to change the general options
of your app through settings screens. It’s not necessary to create a form,
because Android includes a set of classes designed to create settings screens.
The basic class is the Preference, and there are several different preference
forms, mimicking the standard UI form widgets. The user’s preferences will
be saved to a key-value store that is local to your app.
sCrollVieW
Adding entry fields to a form is simple, but what happens if you cannot fit all the
views on one screen? In these cases, it’s often useful to allow scrolling in order to
fit more elements in a single activity. To achieve this effect, you need to wrap your
views in a ScrollView container. A ScrollView allows you to create a view that is
larger than the physical screen on a device and scroll it to reveal the full contents.
ScrollView is actually a subclass of FrameLayout, but it adds the ability to scroll
its content. You typically place another layout container inside the ScrollView to
arrange the child views.
<ScrollView xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:fillViewport=”true” >
<LinearLayout>
<!-- Rest of code here -->
</LinearLayout>
</ScrollView>
A frequent task is displaying a block of text with a button at the bottom (such
as in a license agreement to which a user must agree). Figure 4.6 shows the desired
result: a long block of text that scrolls to reveal a button. When the text is smaller
than a single screen, the naive implementation of ScrollView results in Figure 4.7—
the button should still be pinned to the bottom of the screen but is instead directly
below the text. The ScrollView only takes up as much space as its content. To fix
this, set the fillViewPort attribute to true. Here is the code to correctly imple-
ment scrolling for any size of text, resulting in Figure 4.8.
android:orientation=”vertical” >
<TextView
android:layout_width=”fill_parent”
android:layout_height=”0dp”
android:layout_weight=”1.0”
android:text=”@string/hello” />
<Button
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”Button” />
</LinearLayout>
</ScrollView>
Try using ScrollView with and without the fillViewPort attribute to see how
its behavior changes.
Android phones feature large, high-resolution displays that are perfect for displaying
images in your application. Images are an important way of conveying information
to your users without explicitly stating it. Typically, images are displayed using the
built-in image view. This view takes care of the loading and optimizing of the image,
freeing you to focus on app-specific details like the layout and content. Unless you
need special optimizations for your application, you should take advantage of the
built-in image view whenever possible.
<ImageView
android:id=”@+id/image”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:scaleType=”center”
android:src=”@drawable/icon” />
sCale tYPe n
centerCrop Scales the image such that both the x and y dimensions are greater
than or equal to the view, while maintaining the image aspect
ratio; crops any part of the image that exceeds the size of the view;
centers the image in the view.
centerInside Scales the image to fit inside the view, while maintaining the
image aspect ratio. If the image is already smaller than the view,
then this is the same as center.
fitCenter Scales the image to fit inside the view, while maintaining the
image aspect ratio. At least one axis will exactly match the view,
and the result is centered inside the view.
fitStart Same as fitCenter but aligned to the top left of the view.
fitEnd Same as fitCenter but aligned to the bottom right of the view.
fitXY Scales the x and y dimensions to exactly match the view size; does
not maintain the image aspect ratio.
matrix Scales the image using a supplied Matrix class. The matrix can be
supplied using the setImageMatrix method. A Matrix class can be
used to apply transformations such as rotations to an image.
tip: the fitXY scale type allows you to set the exact size of the
image in your layout. however, be mindful of potential distortions
of the image due to scaling. if you’re creating a photo-viewing application,
you will probably want to use the center or fitCenter scale types.
Figure 4.9 shows examples of the scale types. Using the correct scale type is
important if you want to properly display images.
Note: drawing images into a view uses the system’s standard drawing
process. in android versions earlier than 3.0, this process is not fully
hardware accelerated. Be aware that graphics-intensive applications
using this process will not perform well on older versions of android.
The typical Android device ships with a built-in GPS receiver and an always-on
network connection. This provides tremendous opportunities for developers to
leverage these features and create compelling location-aware applications. Android
devices include access to Google’s mapping technology, which you can use to add
full-fledged navigation to your app. And the built-in Webkit browser gives you the
power to create your own web-browsing applications. The next sections cover the
basics of using these advanced views.
maPVieW
Unlike other views and classes in Android, maps are not part of the core library.
They are provided by Google and are available to any application running on an
Android-compatible device. Notably, this does not include devices that do not
conform to the Android Compatibility Definition, such as the Kindle Fire. You will
be unable to use Google Maps on those devices. However, most devices meet the
Android specifications and support Google Maps integration.
2. Using the Android SDK Manager, download the Google APIs version of the
Android SDK that you intend to support. You can use this SDK to create a
new AVD image that supports MapView. Make sure you select a Google APIs
target for your image.
With those tweaks, you can use maps in your application. You add a map view
to your layout like you would add any other view:
<com.google.android.maps.MapView
android:id=”@+id/mapview”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:apiKey=”Your Maps API Key”
android:clickable=”true” />
Note that the element name highlighted in the code is the full package name—
anytime you use a custom view that is not part of the core Android library, you need
to specify the full package name. You will need to declare the ID of the MapView as
mapview. Also, there are two new attributes here. The first is the apiKey attribute,
which is where you will place the Google Maps API key you get from Google. This
enables you to use Google’s mapping service. The second new attribute is the
clickable setting. Setting this to true allows the user to tap and scroll on the
MapView in your UI; setting it to false will prevent all interaction with the map.
To actually use a map view in your layout, your activity will need to extend
MapActivity, which handles all the setup of the map view, and override the
isRouteDisplayed method, which is required by the Google Maps license agree-
ment and should return a Boolean that indicates whether there is active routing
information displayed on the map (Figure 4.10).
Note: Because your activity must extend MapActivity, you cannot use
fragments from the compatibility library and use a map view at the
same time. For android 3.0 and above, the fragment framework is built
in to the Activity class, so this is not an issue.
<WebView
android:id=”@+id/webview”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
You will need to enable the INTERNET permission in your manifest for your
web view to access online webpages. The web view does all downloading and
rendering of webpages, and you won’t need to extend any special activities or use
a special ID. With a web view in your UI, loading a webpage is as simple as adding
the following code:
With that, you can display any webpage to the user in your custom UI layout.
Note that the supplied content highlighted in the example is an actual webpage
URL. It’s also possible to load an arbitrary string containing HTML for display.
The web view defaults don’t include JavaScript or Flash support. To enable that,
you’ll need to use a WebSettings object:
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
The first line indicates that the web view will support zooming its contents. The
second line uses the web view’s built-in zoom controls for performing the zoom
(this includes the tap-to-zoom and pinch-to-zoom functionality).
Finally, you will likely want to override the loading of new URLs in your web
view. If you don’t do so, when the user taps on a new URL in the web view, the
default browser will open to load the new link. To force the load to occur in your
web view, add the following code:
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
Here the URL loading behavior is overridden, and the new URL is loaded in the
existing web view. Returning true will discontinue the propagation of the event
up the view hierarchy and prevent the browser from opening. Figure 4.11 shows
the screen of this activity.
The web view allows you to present any HTML content to the user and pro-
vides an easy way to load pages from the Internet. You should take advantage of it
whenever your application needs to display HTML content.
This chapter introduced the basic building blocks used to build a form on Android.
You used these to refactor the TimeTracker app into a series of activities for display-
ing and entering tasks. You still need to save the data and display it, which we’ll
cover later in the book. In this chapter, you learned that
J Android provides a set of simple input widgets that you can use to build forms.
J Use the proper android:scaleType attribute when displaying an image
using ImageView.
J With the Drawable class, you can create complex image types using only XML.
J Adding a map to your application is as simple as extending MapActivity
and adding the map view to your layout.
J Android’s Webkit-based WebView class allows you to display any HTML content.
139
ABstrActing YOUr lAyouts
The only required attribute of the <include> tag is the layout attribute (note
that it does not include the android: prefix). This attribute specifies the layout
file that will be included. In this example, a new layout called sub_layout will be
included in the existing layout. You can override layout_* attributes of the included
layout’s root view by adding them to the <include> tag. Only the android:layout_*
attributes and the android:id attribute can be overridden; all other attributes are
ignored. The new attributes will be applied only to the root node of the included
layout. This example shows another include tag, but this time with the android:id
and layout attributes overridden:
I
<include
android:id=”@+id/sub_id”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
layout=”@layout/sub_layout” />
After inflation, the ID of the sub_layout root element will be set to sub_id.
Similarly, its android:layout_width and android:layout_height attributes will be
changed to match_parent. Using the <include> tag allows you to abstract common
components in your UI and use them throughout your application.
1. Create a new layout named detail_item.xml. This layout will contain two
TextViews: one for the name of the field and one for the actual text. Here
is the code for the new detail_item.xml layout file:
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:id=”@+id/name”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”horizontal”
android:gravity=”center_horizontal”
android:paddingLeft=”3dp”
android:paddingRight=”3dp” >
I
<include
android:id=”@+id/task_desc”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_marginBottom=”20dp”
layout=”@layout/detail_item” />
By using the <include> tag, you now have six text fields instead of three, but
you have not changed the amount of XML in the task_detail.xml layout.
3. The final piece is adding a convenience method to set the fields, which is
a little more work:
private void setNameAndText(View v, int nameId, String value) {
TextView name = (TextView) v.findViewById(R.id.name);
TextView text = (TextView) v.findViewById(R.id.text);
String s = getResources().getString(nameId);
name.setText(s);
text.setText(value);
}
You now have a much nicer display of task data, all using the same abstracted
layout.
I
...the resulting layout hierarchy after the system merges the included sub-layout
looks like this:
Note: using the <merge> tag requires that the sub-layout be constructed
to fit in the parent layout; for example, you cannot use a layout
designed for a LinearLayout in a hierarchy containing a FrameLayout
(technically this isn’t prevented, but it doesn’t make much sense to do it).
VieWstuBs
While the <include> tag makes it easy to separate your UI into reusable compo-
nents, you may find that your included layouts are rarely used. Layouts such as
progress and error bars need to be available, but they are not shown during normal
usage. When included in your layout, these UI elements are still inflated, taking
CPU cycles and memory even if they are not shown. Luckily, Android provides an
abstraction to solve this problem.
The ViewStub class is an invisible view that takes no space in your layout. Like
the <include> tag, the ViewStub references an external layout that will be added
to your UI. However, unlike what happens with the <include> tag, the referenced
layout is not inflated until you specifically request it to be inflated. With a ViewStub,
the optional parts of your layout are available if you should need them, but they
are invisible otherwise, which speeds up the drawing of your UI.
Here is a simple ViewStub layout that is similar to the previous include example:
<ViewStub
android:id=”@+id/view_stub”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:inflatedId=”@+id/sub”
android:layout=”@layout/sub” />
I
VieW VisiBility
Android views have three possible visibility states, each with a different
effect on your layout:
J View.VISIBLE: The view is visible to the user and takes up space in
the layout.
J View.INVISIBLE: The view is not visible to the user, but continues to take
space in the layout.
J View.GONE: The view is not visible to the user and consumes no space in
the layout.
Visibility can also effect layout performance. A view with visibility set to
GOnE will not be included in the layout and drawing process.
To inflate the external layout referenced by your ViewStub, you can either inflate
it yourself or change its visibility:
The android:id attribute is not applied to the sub-layout (as it is when using
the <include> tag). Since you need to manually inflate the ViewStub, you have to be
able to reference its ID, but you can still set the ID of the inflated sub-layout using
the android:inflatedId attribute. Similarly, the android:layout_* attributes can
be overridden by setting the attributes on the ViewStub.
In addition to abstracting the layout of your UI, you can also abstract the styling
of the UI. Just as you can separate layout from design in websites using Cascading
Style Sheets, you can use Android’s styles and themes to separate the design of
your views from their content. This allows you to quickly change the look of your
app without updating dozens of individual layout files.
stYles
Throughout this book, you have applied appearance-altering attributes to your
views. This is convenient, but it quickly becomes tedious when you have larger
layouts. Android provides the style attribute, which allows you to quickly apply
a new set of attributes to your views.
Styles are defined in an XML file and are applied to views by using the style
attribute. For example, rather than writing this:
<TextView
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:textColor=”#FF0000”
android:text=”@string/hello” />
. . . you can move the highlighted attributes into a style and place the style in an
XML file in the res/values folder. This file can have any name, and you can have
multiple separate style files. For example, imagine a style file named styles.xml.
In this file is a <resources> tag with a series of <style> elements. Here is a style
named RedText:
I
Each attribute is listed as an <item> element. The RedText style specifies that the
android:textColor attribute will be set to red, the android:layout_width attribute
to match_parent, and the android:layout_height attribute to wrap_content.
Now you can write the following, replacing the text view attributes with a single
style attribute referencing the new style:
<TextView
style=”@style/RedText”
android:text=”@string/hello” />
Note that the style attribute has no android: prefix. By using the <style>
tag, you can quickly make changes to the look of your app without updating all
the layout files.
Styles can inherit from other styles by adding the parent attribute to the <style>
tag. Here is a new style that inherits from the system-default text appearance:
The Android platform’s default styles are available in the R.style class. When
referencing them in XML, you use the @android: prefix. Eclipse will autocomplete
these attributes for you. By making styles inherit from the platform-default styles,
you will ensure that your app looks like a native Android application.
The TimeTracker app’s detail text views could still use a little more styling.
1. Create a styles.xml file in the res/values folder, and create the following
styles:
<?xml version=”1.0” encoding=”utf-8”?>
<resources>
<style name=”detail_name”>
<item name=”android:gravity”>right|bottom</item>
<item name=”android:textAppearance”>?android:
p attr/textAppearanceSmall</item>
<item name=”android:layout_marginRight”>3dp</item>
</style>
<style name=”detail_text”>
<item name=”android:gravity”>left|bottom</item>
<item name=”android:textAppearance”>?android:
p attr/textAppearanceLarge</item>
</style>
<style name=”custom_button”>
<item name=”android:layout_marginRight”>3dp</item>
<item name=”android:layout_marginLeft”>3dp</item>
</style>
</resources>
I
These styles will be applied to the name and text fields of the detail_item.xml
layout. The custom_button style adds some margins from the edges of the
screen and will be applied to all the buttons in the app.
2. Open the detail_item.xml file, and set the style of the two TextViews to
the new styles defined in styles.xml:
<TextView
android:id=”@+id/name”
style=”@style/detail_name”
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_weight=”1”
android:text=”@string/detail_name” />
<TextView
android:id=”@+id/text”
style=”@style/detail_text”
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_weight=”3”
android:text=”@string/text” />
3. Apply the new button style to all the buttons in the app. Here is the style
applied to the Edit button in task_detail.xml:
<Button
android:id=”@+id/edit”
style=”@style/custom_button”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”@string/edit” >
</Button>
themes
Styles applied to a view apply only to that view and not to the children of that view.
Even if you apply the style to a ViewGroup such as a LinearLayout, the style will
only apply to that view. To apply the style to the child views, you would need to
set the style attribute for each of those children.
It’s possible to apply a style to all the views of an activity or application with-
out specifying the style attribute of all the views. You do this by applying the
android:theme attribute to the <activity> or <application> elements of your
application manifest. This is known as a theme. Here is an example:
<activity
android:name=”.ExampleActivity”
android:theme=”@android:style/Theme.Holo” >
</activity>
The Holo theme, which is the default theme for all applications targeted at
Android 3.0 and higher, will be applied to all elements of this activity.
I
usiNG FrAgments
When Android made the jump from phones to tablets, Google had to redesign the FIGURe 5 .1 Fragments allow
architecture of applications because Android’s existing UI elements were insuf- you to divide your UI into
logical pieces and display them
ficient to create the type of information-rich interfaces required by tablets. To differently for each device.
address this issue, Google introduced the fragments framework in Android 3.0. Left: Two fragments are
Fragments provide a method for decomposing your UI into its constituent parts displayed at once on a tablet
device. Selecting a list item will
so that each may be presented in a manner that is right for the device it’s running change the content displayed.
on (Figure 5.1). On a phone, the list view would consume the whole screen, and Center: The list fragment takes
tapping an item would take the user to a new screen presenting content. But on a the entire display on a phone.
Right: The detail fragment
tablet, the list view is simply a part of the display, with the content being displayed showing content is reached by
simultaneously. As you can see in Figure 5.1, the list view and the content are each selecting an item from the list
contained in a fragment. fragment on the phone.
Fragments are the future of building interfaces on Android. They allow you to
provide simple UI elements and arbitrarily combine them into new forms. Android
now uses them extensively, and you should strive to do the same in your own
applications. The key to using fragments is to understand how they differ from
and interact with activities.
<FrameLayout xmlns:android=http://schemas.android.com/
p apk/res/android
android:layout_width=”match_parent”
p android:layout_height=”match_parent”>
<fragment class=”com.example.ExampleFragment”
android:id=”@+id/example”
android:layout_width=”match_parent”
p android:layout_height=”match_parent” />
</FrameLayout>
Note that the <fragment> tag is lowercase and has a class attribute. This attri-
bute must reference a fully qualified Java class that extends the Fragment class.
The class attribute is not required, however; when it is left out, you need to add
the fragment at runtime using the FragmentManager. The FragmentManager is the
interface for working with fragments in Java code—you use it to find, add, remove,
and replace fragments. You’ll see more examples of the FragmentManager shortly.
I
tv.setText(“Hello Fragment!”);
return tv;
}
}
onCreate onAttach called when the fragment is first associated with an activity.
onCreate called to initialize the fragment. note that the host activity may not
have finished its onCreate call.
onCreateView called to create the view hierarchy of the fragment. This method
should return the inflated layout for the fragment. note that it is
not required that the fragment have a UI component.
onActivityCreated called when the host activity has finished its onCreate callback. Used for
any fragment initialization that requires the host activity to be initialized.
onStart onStart called when the fragment is visible to the user. Generally called at the
same time the host activity’s onStart method is called.
onResume onResume called when the fragment is visible to the user and actively running. Gen-
erally called at the same time the host activity’s onResume method is called.
onPause onPause called when the fragment is no longer interacting with the user, either
because the activity is paused or the fragment is being replaced. Gener-
ally called at the same time the host activity’s onPause method is called.
onStop onStop called when the fragment is no longer visible to the user, either because
the host activity is stopped or the fragment is being replaced. Generally
called at the same time the host activity’s onStop method is called.
onDestroy onDestroyView called when the view returned by onCreateView is detached from
the fragment.
onDestroy called when the fragment is no longer used. You should clean up any
remaining states here.
onDetach called when the fragment is no longer attached to its host activity.
This just returns the existing task_list.xml layout as the view for the fragment.
I
private void setNameAndText(View v, int nameId,
p String value) {
TextView name = (TextView) v.findViewById(R.id.name);
TextView text = (TextView) v.findViewById(R.id.text);
String s = getResources().getString(nameId);
name.setText(s);
text.setText(value);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
TimeTrackerActivity activity = (TimeTrackerActivity)
p getActivity();
// Initialize the timer
TextView counter = (TextView)
p activity.findViewById(R.id.counter);
counter.setText(DateUtils.formatElapsedTime(0));
Button startButton = (Button)
p activity.findViewById(R.id.start_stop);
startButton.setOnClickListener(activity);
Button editButton = (Button)
p activity.findViewById(R.id.edit);
editButton.setOnClickListener(activity);
View v = activity.findViewById(R.id.task_name);
String text = getResources().getString
p (R.string.task_name);
setNameAndText(v, R.string.detail_name, text);
v = activity.findViewById(R.id.task_date);
text = DateUtils.formatDateTime(activity, date,
p TimeTrackerActivity.DATE_FLAGS);
I
FIGURe 5 .2 The timer layout
of the TimeTracker app
You should now have an app that looks like Figure 5.2. You won’t yet be able to
access the timer list, but in the next chapter you will add some navigation so that
you can quickly switch between the task list and the timer.
FraGmeNt traNsaCtioNs
Because multiple fragments can be displayed onscreen at once, it’s possible to add
and remove them without switching activities. For example, the content portion
of your app (represented by a fragment) can be replaced with a different fragment
when the user selects a different item from a list fragment. This allows you to create
dynamic interfaces that change content as the user interacts with them.
FragmentManager fm = getFragmentManager()
FragmentTransaction ft = fm.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
ft.add(R.id.fragment_container, fragment);
ft.commit();
Here, a new fragment is added to the UI, placed in the view, and given the
ID fragment_container. The FragmentManager also provides the interface for
retrieving the existing fragments in your layout. Fragments can be referenced by
their ID or by a tag string:
fm.findFragmentById(R.id.frag);
fm.findFragmentByTag(“tag”);
I
FraGmeNt BaCk staCk
Like activities, fragments can have a back stack. However, you have direct control
over which fragments are added to the stack and when they are added. Before
committing a transaction, you can add the transaction to the back stack. Here is
an example:
FragmentManager fm = getFragmentManager()
FragmentTransaction ft = fm.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
ft.add(R.id.fragment_container, fragment);
ft.addToBackStack(null); // takes a string name argument,
p not used here
ft.commit();
Later, when the user presses the Back button, the fragment transaction will
be reversed. This actually reverses all the steps of the transaction, including any
transitions.
You also have the option of popping transactions off the back stack yourself
by calling the FragmentManager.popBackStack() method, which simply pops the
last transaction off the back stack; it takes not parameters. Using popBackStack
gives you more control over how your UI behaves, rather than just relying on the
forward and backward paradigm.
In this chapter, you learned the basics of abstracting and componentizing your UI.
By breaking a complex layout into components and altering a few key layout files,
you can quickly change the look and feel of your UI. You also learned the basics
of Android’s powerful fragments framework. Fragments allow you to abstract
your app into functional components and then combine them to create complex
layouts appropriate for phones, tablets, and televisions. Here are the highlights:
J You can include one layout in another by using the <include> and <merge> tags.
J Rarely used layouts can slow down the drawing of your UI. For those situ-
ations, use a ViewStub.
J You can change the look and feel of your entire app by creating and applying
a theme to your activities.
J You should use fragments to break an app into separate, reusable components.
J Changes to the displayed fragments must be performed in a fragment
transaction.
I
This page intentionally left blank
6
nAVigAtion AnD
DATA loAding
This chapter builds on previous chapters by showing
you how to create navigation within your app. You will
learn that the action bar replaces the options menu and creates
consistent functionality across Android apps; that tabbed inter-
faces leverage the action bar, but you need to fall back to TabWidget
on older versions of Android; that the ViewPager class lets you add
side-to-side swiping navigation to your application; that adapters
are used to bind your data to your displayed views; and that load-
ers offer a simplified method of obtaining the data for your UI.
165
introducing the Action BAr
Starting with version 3, Android gained a major new UI paradigm called the action
bar (Figure 6.1). The action bar sits at the top of the screen and contains the app
name, the app icon, navigation elements (such as tabs), and a series of buttons for
quick actions. Using this native UI element, Android developers can quickly add
functionality to their apps and create a platform-consistent user interface.
aCtioN items
The action bar replaces the traditional menu found on pre-3.0 versions of Android.
Instead of a hidden set of options revealed by pressing the menu button, menu
options are presented as buttons on the action bar. Not all menu options can fit, of
course, so by default menu options are placed in an overflow menu that appears at
the end of the action bar. Tapping the overflow menu drops down a list revealing
the remaining options. When developing an app, the developer chooses which
options should be shown as actions on the action bar.
In this example, a search menu item will be displayed as an action item. Note
that the android:showAsAction attribute also has the withText option set—this
declares that both the icon and the title text of the menu item will be displayed in
the action bar. The action items are added to the action bar automatically when
you inflate your menu:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
Android 4.0 introduced a new option for the action bar: the split action bar. In this mode, the action bar is
split between the top and bottom of the screen, with navigational elements at the top and action items at
the bottom (Figure 6.2). This mode is found throughout Google’s applications, including in the Gmail app.
To enable this, add an android:uiOptions attribute to the <application> or <activity> elements in the
AndroidManifest.xml file, and set it to splitActionBarWhenNarrow. Here’s an example, enabling a split action
bar on an activity:
<activity
android:name=”.SampleActivity”
android:uiOptions=”splitActionBarWhenNarrow” >
</activity>
On narrow-screen devices, the action bar will now show the action items in a bar along the bottom of the
screen. On larger devices, like tablets, the actions will continue to be displayed at the top of the screen. note
that you can safely include the android:uiOptions attribute in your manifest for older versions of Android—
unknown manifest attributes will be ignored by the system.
The overflow menu will be added to the action bar if none of the menu items
are action items.
Note: the action bar aPi is not available on android versions before 3.0.
if you want to use the action bar in older versions of android, you will
have to create your own layout and populate it with action items.
alternatively, you can use the open-source library actionBarsherlock
(http://actionbarsherlock.com).
aCtioN VieWs
The action bar is more than just a row of buttons. It can also display interactive
widgets called action views, which provide enhanced functionality beyond just a
tappable button. A good example is the search widget: By default, a search icon is
displayed; when the user presses the icon, a search widget appears, providing an
area where the user can type a query (Figure 6.3). You will see this used throughout
Android apps to provide rich functionality beyond simple option selection.
Take a look at the code for the action view shown in Figure 6.3. To make this
action item into an action view, add the android:actionViewClass attribute and
set it to the desired view:
Here, the search icon will activate the Android-provided SearchView widget,
which displays a text-entry box. Note that the android:showAsAction attribute
has a new setting: collapseActionView. This attribute is new to Android 4.0 and
will collapse the action view into just an action item. Otherwise, the search widget
would consume space on the action bar even when not in use.
You then just have to add the tabs and their listeners to the action bar:
Tab t = bar.newTab();
t.setText(“Tab Text”);
t.setTabListener(this);
bar.addTab(bar.newTab()
In this example, the content view is replaced with a new fragment when the
users selects a tab, and that fragment is removed when the tab is no longer selected.
You will need to supply both a SpinnerAdapter to bind data to the list and an
implementation of the OnNavigationListener interface to handle callbacks trig-
gered when a list item is selected. You will learn more about data binding later in
this chapter.
To implement tabs using a TabWidget, you need both a TabWidget and a TabHost.
TabHost is a container that you set as the root of your layout hierarchy. Inside it,
you place a FrameLayout and a TabWidget. The TabWidget is the switchable list
of labeled tabs. Typically you would arrange the FrameLayout and the TabWidget
inside a LinearLayout, but you’re not required to. Unfortunately, this style of tabs
is not completely compatible with the fragments API and requires some hacks to
integrate with fragments.
Here is an example implementation of a tab layout:
<TabHost
xmlns:android=http://schemas.android.com/apk/res/android
android:id=”@android:id/tabhost”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>
<LinearLayout
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >
<TabWidget
android:id=”@android:id/tabs”
android:orientation=”horizontal”
Note that the IDs of the TabHost and TabWidget are system IDs—the tab classes
require this to properly function. When the user changes tabs, the system will
find the FrameLayout with ID android:id/tabcontent and change its content.
However, those APIs are deprecated, and you should use fragments to swap the
layouts in your UI. To handle this, you set the size of the tabcontent layout as 0
and instead place all your content in a second FrameLayout (this example uses the
ID realtabcontent).
You are required to call setup on the TabHost before you add any tabs to it.
You register a listener for changes in the active tab. The onTabChanged method
will be called every time the user presses one of the tabs. You then add the tabs
to the TabHost.
Finally, notice the DummyTabFactory. This is another hack that enables you to
use fragments with the TabWidget. The following example has a simple imple-
mentation that just returns a 0-sized view to match the API required by the
TabContentFactory interface:
Swipe
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
You can now implement the logic in onTabChanged to swap in a new layout
(using fragments) when the user presses a tab.
VieWPaGer
When Google released Android 3.0, they also released the compatibility library—
a collection of classes that brings the new APIs to older platforms. In addition,
the compatibility library allows Google to ship new classes that they don’t yet
want to add to the official Android release. The compatibility library contains
one such class named the ViewPager. The ViewPager is similar to TabHost, but in
the ViewPager—instead of pressing labeled tabs to switch views—the user drags
the entire display to the left or right to switch pages (Figure 6.6). This is the same
behavior used by the Android home screen to switch “pages.” This new behavior
is available to any application that implements the ViewPager class.
4. Override the getCount and getItem methods. The getCount method should
return the total number of fragments in the pager; the getItem method
returns the fragment at the specified position. These methods will be called
as the user scrolls through the pages. Adjacent fragments are acquired before
they are visible so that the UI can display them while the user is paging.
@Override
public int getCount() {
return 3;
}
5. For this example, create a simple fragment that just displays a line of text:
public class SimpleTextFragment extends Fragment {
private int mPosition;
public static SimpleTextFragment newInstance(int position) {
SimpleTextFragment frag = new SimpleTextFragment();
frag.mPosition = position;
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup
p container, Bundle savedInstanceState) {
TextView tv = new TextView(getActivity());
tv.setText(“Page “ + mPosition);
return tv;
}
}
In Chapter 2, you learned about list views and how you bind data to the list. The
ListView class is actually an implementation of the more general AdapterView.
Adapter views are used to display data that is not known until runtime; these
take the form of lists, spinners, grids, or anything else where a series of similar
elements is displayed. To bind the data to the display, you use the Adapter class.
The adapter takes data from a data source, inflates and configures the item views,
and then loads the data into the adapter view. The data source is typically an array
or a database cursor. You will use adapters and adapter views throughout your
Android applications.
You saw an example adapter in Chapter 2 when you implemented a list view.
In that example, you created an adapter that extended ArrayAdapter to display in
a list. The TimeTracker app will use a database for storing tasks, so you’ll need to
implement a new adapter that extends CursorAdapter. This is also a good time to
learn about an important optimization that you can use in your adapters.
loaders
Before version 3.0, Android required a complex set of method calls to query the
database asynchronously and retrieve a cursor. Android 3.0 introduced loaders to
make loading, watching, and re-querying data a much simpler task. Loaders auto-
mate the grunt work of querying your database and returning usable data to your
app. They also monitor their data source for changes and will call into your app
when something changes. You should absolutely be using loaders in your applica-
tion, because they will greatly simplify your data binding code, and thanks to the
compatibility library, you can use loaders with Android versions earlier than 3.0.
The loader ID is used only within the context of your app. It is needed to
distinguish between loaders when using the same LoaderManager callbacks
for more than one loader.
3 . Implement the LoaderCallbacks, providing data for the loader and actions
to take when the data changes.
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri = CONTENT_URI;
return new CursorLoader(getActivity(), uri, null, null,
p null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
public void onLoadReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
The CursorLoader in this example will query a database using a static URI and
query parameters (null in this case results in the default data being returned).
When onLoadFinished is called, it swaps the new cursor into the adapter that will
then trigger the view to be updated. In the case that the data is reset, the adapter
is configured with null data, clearing the display.
This chapter introduced Android’s new action bar view, a simplified bar that provides
quick actions for Android applications. You learned about the options for adding
navigation to your app, including the ViewPager, which is used extensively through-
out the Android 4 release. The TimeTracker app has really come together now that
you’re loading data from a database into the UI. In this chapter, you learned that
J The action bar replaces Android’s menu button and provides a unified look
across applications.
J You can use an action view to provide a search interface that is integrated
into the action bar.
J The action bar provides a simple tabbed browsing interface you can easily
add to your app.
J The ViewPager class, available in the compatibility library, creates a paging-
style interface.
J You can use loaders to asynchronously query and load data into your UI.
189
creAting a BasiC widget
This code declares an AppWidgetProvider class (more on that in a bit) and gives
it a name. In this case, it’s TimerWidgetProvider. Notice that the XML element is
a <receiver>. This declares it as a BroadcastReceiver, similar to the one you used
for communicating between the TimerService and the TimeTrackerActivity.
The AppWidgetProvider class is really just a BroadcastReceiver. You’ll learn more
about the AppWidgetProvider class in a little bit, but just know that you have to
specify the <intent-filter> and that it should filter on the android.appwidget.
action.APPWIDGET_UPDATE broadcast. You also need to declare the <meta-data>
element that specifies the AppWidgetProviderInfo XML file.
1. Open the TimeTracker app, and create a folder named xml in the res/ directory.
You will also need to create the layout used by the widget. For the TimeTracker,
the widget will show a simple box with the time and a Start/Stop button
(Figure 7.2).
n n
minHeight, minWidth The default width and height that your widget will be given
when created. note that the widget may be given more space
than requested. This should be in units of dp. Use the formula
(70 x n) – 30, where n is the number of cells your widget needs.
(See the sidebar “How to calculate Widget Size” for more info.)
minResizeHeight, The absolute minimum height and width that your widget can
minResizeWidth be resized to. If greater than minWidth and minHeight, this is
ignored.
label The label the user will see when selecting a widget.
icon The icon the user will see when selecting a widget.
initialLayout The XML layout file used by your widget. note that the final
widget layout may change if the user resizes it.
resizeMode Set this to allow users to resize your widget. Valid values are
NONE, VERTICAL, HORIZONTAL, or BOTH. The default is NONE.
autoAdvanceViewId Used with a collection widget, this specifies the view ID that
should be auto-advanced by the widget’s host.
You have now completed all the layout work for the widget.
The Android home screen is divided into cells arranged in a grid. Generally,
phones have home screens with a 4 by 4 grid, and tablets have home screens
with an 8 by 7 grid, but device manufacturers may create home screens with
grids of any size.
Here are some tips to ensure that your widgets function properly:
J Widgets will expand to the grid size that contains them. So if the cell size
is 10dp by 10dp and your widget requests a size of 15dp by 15dp, it will
consume 4 cells on the grid (2 high and 2 wide).
J The minimum size of a widget is 1 cell.
J To calculate the size of your widget, use the formula (70 x n) – 30, where
n is the desired number of cells. So, if you want your widget to be 4 cells
wide, set the minWidth attribute to (70 x 4) – 30 = (280) – 30 = 250dp.
J Widgets should not extend to the edges of their containers; they should
have padding such that there is space around the visible part of the
widget. Android 4.0 adds padding to the widget layout for you, but you
should add some padding for previous versions of Android. Unlike normal
layouts, you cannot use resource qualifiers for widget layouts. To create
padding for a widget layout only on pre-4.0 devices, use dimens or differ-
ent drawable resources.
J For maximum compatibility, never make a widget that requires more
than 4 by 4 cells.
method n
onEnabled called the first time a widget is created, but not for subsequent
widget creations. Use this to perform a one-time initialization,
such as instantiating database cursors that all app widgets
can share.
onDisabled called when the last instance of a widget is deleted. Use this to
clean up any operations performed in onEnabled.
The RemoteViews object doesn’t actually contain a view hierarchy, but rather
contains the necessary information to create one. Since the views don’t exist, the
RemoteViews object doesn’t allow direct manipulation of the view attributes. Instead,
you set view attributes using methods of the RemoteViews object itself, supplying
the ID of the view to be updated.
Add the rest of the TimerWidgetProvider implementation.
This method simply creates a new PendingIntent that will send an Intent to
the TimerService. This triggers the starting and stopping of the timer. Note
that the setOnClickPendingIntent takes the view ID as the first parameter.
Remember that a remote process inflates the views in a RemoteViews object,
so you cannot set the fields directly. Finally, you update the widgets by call-
ing updateAppWidget on the AppWidgetManager.
Now you should have a working widget for the TimeTracker app. You can use
this to start and stop the timer.
1. Add the configuration activity to the widget’s provider info XML file:
<appwidget-provider xmlns:android=http://schemas.android.com/
p apk/res/android
...
android:configure=”com.example.android.
p ExampleAppWidgetConfigure” >
</appwidget-provider>
FIGURe 7 .3 The view types Android 3.0 introduced a new type of widget that can display a collection of data
available to display in a provided by a remote data source such as a ContentProvider. The widget itself
collection widget. From
left to right: a ListView, displays this data in a list, grid, stack, or picture frame. Creating one of these widgets
a GridView, a StackView, is exactly like creating a standard widget but requires a new RemoteViewsService
and an AdapterViewFlipper. and RemoteViewsFactory to supply the collection data.
The ListView and the TextView are contained inside a FrameLayout so that
the TextView can be displayed when there is no data. Each item displayed by the
widget will need a layout as well. Since this example uses a ListView, you can just
use the standard android.R.layout.simple_list_item_1 layout.
The factory is used by the host activity to retrieve the data to display. The
getViewAt method is where the RemoteViews object is returned. Here, the standard
list item layout is used when populating the ListView; note that this example
returns ten results. Finally, the widget must be set up to use the service in the
AppWidgetProvider:
You should now be able to run the example and create a widget similar to the
one shown in Figure 7.4.
Android widgets provide a convenient method by which apps can present data to
the user without the need to load the full application. This is a powerful addition
to your application’s feature set. Using widgets, users can quickly find the informa-
tion they need without needless device interaction. This chapter covered the basics
of creating widgets on Android. Along the way, you learned that
J An AppWidgetProvider is really just a BroadcastReceiver that handles the
widget update broadcasts for you.
J The RemoteViews class allows widgets to be created and updated by other
processes in Android.
J The AppWidgetProviderInfo XML file is used to configure the widget and
its behavior.
J Collection widgets display data from a service using an adapter-like class
named RemoteViewsFactory.
WrApping Up 211
This page intentionally left blank
Part 3
ADVAncED
ui deVelopment
8
HAnDLInG
gestures
The basic Android UI toolkit provides the most com-
mon interaction gestures you will need in your appli-
cation: taps, long presses, and swipes. But sometimes the
built-in gestures aren’t sufficient for your application or are not
available on the view you’re using. For those cases, Android pro-
vides the ability to create custom gestures. This chapter introduces
the basics of detecting and responding to gestures. Along the way,
you’ll learn that all views have an onTouchEvent method you can
use to intercept touch events; that the MotionEvent class is the
low-level interface to the touchscreen inputs; that you should use
GestureDetector and its subclasses to enable the most common
gestures; and that you can enable pinch-to-zoom functionality by
using the ScaleGestureDetector.
215
listening to toucH eVents
The most basic form of gesture recognition is the touch event. Touch events are
the lowest level of user interaction with the touchscreen. Events include putting
a finger on the screen, sliding a finger along the screen, and lifting a finger off
the screen. Each of these represents a discrete touch event. There are two ways
you can listen for these events: by registering an OnTouchListener on a view and
implementing the onTouchEvent method, or by implementing your own view and
overriding its onTouchEvent method. These methods are called with a MotionEvent
object containing information about the event.
Here is a simple example class that overrides View and implements the
onTouchEvent method:
2. Add some fields for position coordinates and some pre-computed color and
font size values. Create a new Paint object to store the text color and size:
public class TouchExample extends View {
private Paint mPaint;
private float mFontSize;
private float dx;
private float dy;
public TouchExample(Context context) {
super(context);
mFontSize = 16 * getResources().getDisplayMetrics().
p density;
mPaint = new Paint();
3. Override the onDraw method, and use the dx and dy values to set the posi-
tion of the text:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String text = “Hello World!”;
canvas.drawText(text, dx, dy, mPaint);
}
Here, the text is drawn onto the canvas at the specified position, using the
Paint object.
4. Now override the onTouchEvent method, set the x and y coordinates based
on the input MotionEvent object, and finish by invalidating the view:
@Override
public boolean onTouchEvent(MotionEvent event) {
dx = event.getX();
dy = event.getY();
invalidate();
return true;
}
5. Finally, create an activity that uses your new view as its content:
public class GestureActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TouchExample view = new TouchExample(this);
setContentView(view);
}
}
When you run this app, you should now see the text “Hello World!” follow your
finger around the screen (Figure 8.1). This simple example shows how you can
listen to, and take action based on, the user’s touch input. But the example only
handles a single finger. How would you handle a multi-touch screen?
n n
ACTION_POINTER_UP A single finger has been removed from the screen, but
not the finger corresponding to the primary pointer.
ACTION_UP The user has stopped touching the screen and has lifted
all fingers away from it.
The earlier example always uses pointer index 0 to place and move the text. If
you place a finger on the screen, the text will follow it. Try altering the example
to show each touch pointer index and ID:
1. Add both a new class to hold the pointer data and an array to store the point-
ers. For this example, you only need to track five points. Initialize the array
with Pointer objects. You can remove the dx and dy fields:
final static int MAX_POINTERS = 5;
private Pointer[] mPointers = new Pointer[MAX_POINTERS];
class Pointer {
2. Update the onTouchEvent method to track each finger that is placed on the
screen. Set the pointer data when the first touch occurs and while the user
swipes their finger across the screen:
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointerCount = Math.min(event.getPointerCount(),
p MAX_POINTERS);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_MOVE:
// Clear previous pointers
for (int id = 0; id<MAX_POINTERS id++)
mPointers[id].index = -1;
// Now fill in the current pointers
for (int i = 0; i<pointerCount i++) {
int id = event.getPointerId(i);
Pointer pointer = mPointers[id];
3. Update the onDraw method to display the index and ID of each pointer:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Pointer p : mPointers) {
if (p.index != -1) {
String text = “Index: “ + p.index + “ ID: “ + p.id;
canvas.drawText(text, p.x, p.y, mPaint);
}
}
}
Now as you press each finger on the screen, you will see text showing the index
and ID of each pointer (Figure 8.2). Experiment with touching multiple fingers at
once and then lifting them off the screen. Observe how the ID remains consistent,
but the index changes.
Note: You should always handle the ACTION_CANCEL event, because the
touch system may erroneously report touches and cancel them later.
this will also happen when a parent view steals the touch event. For
example, if your view is inside a ListView, then the list may steal the
touch event once it has detected that the user is actually scrolling the list.
Touch events provide a very low-level interface to the touchscreen that can be
difficult to interpret. Often what you really need is the ability to recognize certain
gestures the user makes on the screen. For that, Android provides convenience
classes that detect gestures for you.
GesturedeteCtor
To detect gestures, you create an instance of the GestureDetector class and send
it all touch events your view receives. You register an OnGestureListener with
the GestureDetector to receive callbacks when gestures are detected. This inter-
face has callbacks for the most common gestures you will need: taps, double taps,
swipes, and flings.
Modify the example to zoom the text when you double-tap the screen:
1. Add a scale field to the View. You will use this to scale the text size:
public class TouchExample extends View {
private float mScale = 1.0f;
...
2. Create the gesture listener that will zoom the text. In this case, extend the
SimpleOnGestureListener, which provides stub implementations for the
callback methods. Override the onDoubleTap method to listen for double-
tap gestures:
public class ZoomGesture extends GestureDetector.
p SimpleOnGestureListener {
private boolean normal = true;
@Override
public boolean onDoubleTap(MotionEvent e) {
mScale = normal ? 3f : 1f;
mPaint.setTextSize(mScale*mFontSize);
normal = !normal;
The listener simply alternates the scale factor based on the Boolean normal
value and updates the text size. When finished, it invalidates the view to
force it to redraw.
4. You must call the onTouchEvent method of the gesture detector in the
onTouchEvent method of the view:
@Override
Now when you run the app, double-tapping the screen will zoom the text
(Figure 8.3). Double-tap again and the text will return to its original size. This
example shows how easy it is add simple gesture detection to your app. Adding a
similar double-tap gesture to the TimeTracker app will make starting and stopping
the timer a snap. Go ahead and register an onTouchListener on the timer text view
and add a double-tap gesture that acts exactly like a press of the Start/Stop button.
The GestureDetector makes working with gestures a snap, but it is missing
one important and very frequent gesture: pinch to zoom. For that reason, Android
2.2 added the ScaleGestureDetector.
sCaleGesturedeteCtor
The ScaleGestureDetector class functions exactly like the standard GestureDetector,
but it provides gesture recognition for two-fingered motions. Callbacks are avail-
able for accessing the distance between two pointers, the scale change between
the current and previous pointers, and the focal point of two pointers. The most
common use for this is the pinch-to-zoom feature, where a user presses two fingers
2. To use this gesture in the example app, you need to add the ScaleGesture
Detector to the view onCreate method:
Now, in addition to being able to double-tap to zoom, you can pinch to zoom
as well. With ScaleGestureDetector and GestureDetector, you will be able to
handle all the common touchscreen interaction that users will expect.
Custom gestures
You are not limited to the predefined gestures provided by the Android
framework—you are free to implement your own gesture detection. Follow
the example of the existing gesture detectors; the source code is available at
http://source.android.com/.
There is another option for implementing custom gestures that doesn’t require
custom detection code. The Android emulator contains an app called Gestures
Builder that lets you create gesture patterns by drawing on the screen with
your mouse. These patterns are saved in a file that you can bundle with your
app. By loading this gestures file at runtime, you can register to receive a call-
back when the user performs the gesture.
This chapter introduced the basics of touch events and gesture handling. You
learned how to intercept touchscreen events, what actions correspond to those
events, and how to detect higher-order touch events like gestures. Along the way,
you learned that
J The View.onTouchEvent method is called when a user touches that view.
J Each touch point on a multi-touch screen is called a pointer.
J MotionEvent objects contain all touch pointers and their recent history.
WrApping Up 229
9
AnimAtion
Animation helps users understand the functionality
of your app without the need for explicit instruction.
Android provides a few animation APIs with different use
cases. Drawable and view animations offer the best compat-
ibility, but they only operate on views. Starting with Android 3.0,
the property animation framework is the preferred method for
creating animations. This framework removes the limitations of
view animations and can animate any object, not just views.
In this chapter, you’ll learn that you can use drawable animations
to create sprite-style animation; that the view animation frame-
work provides compatibility for older versions of Android, but that
it can be used only on views; that the ObjectAnimator is used to
change the properties of an object over time; that interpolators
change the rate at which an animation is applied, creating a more
natural animation; and that the ViewPropertyAnimator lets you
animate views using a series of chainable method calls.
231
CreatiNG drAwABle
AnimAtions
4. Create the activity, and set its content view to the layout. Set a touch listener
on the image view to start the animation when the user taps it. Because
the animation is a one-shot, you need to call stop() before start() for
subsequent taps:
public class AnimationExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView iv = (ImageView)
p findViewById(R.id.image_view);
iv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
ImageView iv = (ImageView) v;
AnimationDrawable ad = (AnimationDrawable)
p iv.getDrawable();
ad.stop();
ad.start();
5. Run the app, and tap the screen to see the circle animating as the system
swaps between each drawable (Figure 9.1).
This simple method is very fast to set up and works well when you want a
sprite-type animation.
The primary method of creating animations prior to Android 3.0 was the view
animation system. View animation provides a series of built-in animation opera-
tions that generate in-between, or tween, views. You supply the starting and ending
values, and the animation framework will transform the displayed view. This system
is simple to implement, but it has a few drawbacks. First, it’s limited to operating
on View objects. If you want to animate something that is not a view, you’ll need
to do so yourself. Second, it operates only on a default set of properties; it does
not affect other properties. Finally, view animation alters only how the view is
drawn, not where it is positioned. Importantly, the rectangular hit area of views
is not moved during a view animation—a common bug found in animation code.
You need to alter the view after the animation is finished to update its position.
These limitations led to the deprecation of the view animation framework in
Android 3.0 and later. However, there are still a large number of pre-3.0 devices in
use, so when possible you should use the view animation framework for maximum
app compatibility.
deFiNiNG aNimatioNs
Like most UI code in Android, animations can be defined either in code or in XML.
It’s preferred to use XML, however, because it is easier to create complex animations
and is easily reusable. View animations defined in XML are placed in the res/anim/
folder. The basic structure of an animation is similar to that of a view. These are the
available options for view animation:
J translate, which moves a view
This should look familiar from other Android XML formats. The attributes here
set the duration and the ending position of the animation. The position is specified
as a percentage of the view size—this animation will move the view down and to
the right. Duration is always expressed in milliseconds. Position can be specified
as a percentage of the view size, as a percentage of the parent view size, or in pixel
values (with no units). This same animation can be created in Java code as follows:
TranslateAnimation anim =
new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0.0f,
TranslateAnimation.RELATIVE_TO_SELF, 0.25f,
TranslateAnimation.RELATIVE_TO_SELF, 0.0f,
TranslateAnimation.RELATIVE_TO_SELF, 0.25f);
anim.setDuration(500);
Animations can be grouped into sets, with several animations occurring simul-
taneously. Here is the same example, but this time grouped in a set with another
animation that changes the transparency of the view:
This animation will cause the view to be moved down and to the right, while
simultaneously fading out. The fromAlpha and toAlpha attributes define the begin-
ning and ending alpha transparency, where 1.0 is fully opaque and 0.0 is fully
transparent.
In the example, the animations occur simultaneously, but that is not required.
You can have them occur in sequence, or partially overlapping, by defining the
android:startOffset attribute. Set this to a value in milliseconds to have the ani-
mation wait until that time offset before starting. Here is the same example again,
but this time with the alpha animation beginning after the translate has finished:
You can use this attribute to make several animations occur in sequence. In
addition, you can nest animation sets to create animations that are even more
complex. Check the Android documentation for the full list of animation options.
<translate xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:duration=”500”
android:toYDelta=”50%”
android:toXDelta=”50%”
android:interpolator=”@android:anim/accelerate_interpolator” />
Now when you run the animation, it will start slowly and accelerate until it
reaches the end of the duration.
usiNG aNimatioNs
To use an animation in your application, you have to apply it to a view and run
it. You do this by calling the startAnimation method on the view, passing it the
animation you want to run:
This animation will now cause the TextView to animate down and to the right.
However, once the animation is finished, the view will return to its previous position.
Remember that view animation alters only how the view is drawn, not the actual
view object itself. When the animation is finished, the view is drawn once again
and appears to return to its previous position, because it never really moved at all.
To address this, you can use an AnimationListener to change the view once its
animation has finished. Here is an example that listens for the end of the anima-
tion and makes the view invisible when finished:
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
tv.setVisibility(View.INVISIBLE);
}
});
tv.startAnimation(animation);
1. Create a layout called digit.xml that will contain the two TextViews, and
set one to be invisible by default:
<?xml version=”1.0” encoding=”utf-8”?>
<FrameLayout xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent” >
<TextView
android:id=”@+id/text1”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:text=”0”
android:textSize=”50sp” />
<TextView
android:id=”@+id/text2”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:text=”1”
android:textSize=”50sp”
android:visibility=”invisible” />
</FrameLayout>
3. Add the logic to set the time and trigger the animations. Start by calculating
the hours, minutes, and seconds:
long hours = 0;
long minutes = 0;
long seconds = 0;
if (time > 3600*1000) {
hours = time/(3600*1000);
time -= hours*3600*1000;
}
if (time > 60*1000) {
minutes = time/(60*1000);
time -= minutes*60*1000;
seconds = time/1000;
if (time > 1000) {
seconds = time/1000;
time -= seconds*1000;
}
5. Create the animateDigit function; this function takes a digit layout ID and
a value, and it sets the digit while animating the transition. Next, prevent
the animation from running if a previous animation has not finished. Then
create an animation listener that sets the proper digit value once the ani-
mation is complete:
private void animateDigit(final int id, final long value) {
final View v = findViewById(id);
final TextView text1 = (TextView)
p v.findViewById(R.id.text1);
final TextView text2 = (TextView)
p v.findViewById(R.id.text2 );
boolean running = false;
if (text1.getAnimation() != null)
running = !text1.getAnimation().hasEnded();
if (Long.parseLong(text1.getText().toString()) ==
p value || running) return;
Animation animation = AnimationUtils.loadAnimation
p (this, R.anim.slide_out);
animation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
Note: this code could be made more efficient by performing the view
lookups at activity creation time and caching them for later use. You
could also use a single animation listener, rather than creating one on
every animation. however, for the timetracker app, this code is sufficient.
6. Create the sliding animations for the two text views. These should be familiar
by now. Here is the slide in animation:
<?xml version=”1.0” encoding=”utf-8”?>
<set xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:interpolator=”@android:anim/
p accelerate_interpolator” >
<translate
android:duration=”250”
android:fromYDelta=”-50%” />
<alpha
7. Run TimeTracker, and you will now see a nice animated clock (Figure 9.3).
As you can see, working with view animations is quite simple. However, the
limitations of view animations required a reworking of the Android animation
system. For 3.0 and later devices, it’s recommended you use the property anima-
tion framework.
ValueaNimator
The base class for all property animation is ValueAnimator. This class takes a starting
value, an ending value, and a duration, and it calculates a new value at each time step
of an animation; the time steps are determined by the speed of the animation thread.
The ValueAnimator requires an Interpolator and a TypeEvaluator to compute the
property values. Here is a brief explanation of each class:
J ValueAnimator. This is the base class for all animators. At each frame of
animation, it calculates the percentage of the animation that is completed,
based on the animation’s starting time and duration. It then calls the
Interpolator and TypeEvaluator to calculate the new property values.
Here, an animation is created and set to run for a duration of 250 milliseconds,
starting at a floating point value of 0 and stopping at the value 1. The interpolator
is the default accelerate-decelerate interpolator. Notice the ofFloat method? That
method specifies that the values are floats and that a float TypeEvaluator should
be used. There are also ofInt and ofObject methods for creating animations with
integers and generic Object properties. In the case of objects, you will need to
supply a custom TypeEvaluator for the ValueAnimator to produce correct output.
This code does not yet do anything useful, because the values computed by
the ValueAnimator are not used. You can register an AnimatorListener and a
ViewAnimator.AnimatorUpdateListener to listen for animation events and make
updates accordingly. However, Android provides a convenience implementation of
ValueAnimator called ObjectAnimator that performs updates for you.
If you follow those rules, you can animate any object. For some properties of
views, you may need to call invalidate on the view to force it to redraw. By default,
most of the view properties will do this for you. See the Android documentation
for the full API requirements.
Note: When setting the property value, be sure to use the camel-case
version of the property. While the ObjectAnimator will set the initial
letter to the correct case, any other letters will need to be properly
cased. For example, to call setTranslationX, you would supply the
string “translationX”. in addition, the ObjectAnimator uses reflec-
tion internally to set the properties of objects. this can be resource
intensive, so be mindful of animation performance.
As you can see, this allows you to statically check the property matches before
deploying your code. You can create your own Property implementations to achieve
type correctness when animating your objects.
aNimator sets
Property animations can be grouped into sets, just like view animations. To cre-
ate a set, you instantiate an AnimatorSet object and add animations to it. The
AnimatorSet object has several methods for defining how animations are grouped.
Here is a simple example that demonstrates the basics:
View v = findViewById(R.id.test);
AnimatorSet set = new AnimatorSet();
ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f);
ObjectAnimator slide = ObjectAnimator.ofFloat(v, View.TRANSLATION_Y,
p 100f);
ObjectAnimator scale = ObjectAnimator.ofFloat(v, View.SCALE_X, 2f);
set.play(alpha).with(slide).after(scale);
set.setDuration(1000);
set.start();
This is very similar to the XML for view animations, but the attributes are a
little different. First, you must supply the property name that you wish the anima-
tion to update; if the property doesn’t exist on the object, then the animation does
nothing. Next, you must supply a value type; in this case, they are all floats. Finally,
just as with a view animation, you supply the duration and ending value. To load
and use this animation, add the following code:
You can also define the ordering of the animations in a set. Set the optional
android:ordering property to sequential to play the animations one after the
other, rather than simultaneously.
Android 3.0 added better support for drawing 2D graphics using hardware
acceleration. Previous versions of Android used more software operations for
graphics rendering. To add hardware acceleration support to your application,
add the android:hardwareAccelerated=”true” attribute to the <application>
tag in your application manifest. All applications targeting API level 14 or
above have this setting turned on by default.
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator anim = ObjectAnimator.ofFloat(view, “alpha”, 1f);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
anim.start();
This code will move the text view to position 200 x 200 on the display, while
changing the alpha from its current value to 0. Note that it never calls start. By
default, the ViewPropertyAnimator is started immediately. Setting a start offset
will delay the beginning of the animation.
The expressive nature of the ViewPropertyAnimator API makes it very power-
ful. Consider using it to create animations when you are dealing only with views
and common view properties.
Alternatively, you can use layout transitions with a simple one-line XML change:
<FrameLayout xmlns:android=”http://schemas.android.com/
p apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:animateLayoutChanges=”true” >
</LinearLayout>
This chapter covered the basics of animation on Android. There are currently
three animation frameworks: drawable animation, view animation, and property
animation. Drawable animations are simple to implement but require a different
image for each frame of animation. View animations can be used only on views
and have been deprecated in favor of property animations. Property animations
are very powerful and allow you to apply time-based functions to any object. In
this chapter, you learned that
J Drawable animations create sprite-style animated graphics by rapidly
switching images.
J You should define your animations using XML for easy reusability.
J Android supplies several interpolators that alter how your animation is
applied over time.
J You can use the ObjectAnimator class to animate properties of any object.
J The ViewPropertyAnimator class provides a very compact API for animat-
ing View objects.
J You can animate changes to your layout by adding the android:animate
LayoutChanges attribute to your layout view groups.
WrApping Up 255
10
creAting
cUSTOM Views
The Android view framework provides a rich visual
toolkit with which to create your application UI. How-
ever, you may find that the functionality you need is not
available in the stock views. For those cases, Android allows you
to create a custom view that will better match your needs. In this
chapter, you will learn that Android draws views in two passes, a
measure phase and a layout phase; that you should override the
onMeasure and onDraw methods when creating a custom view;
that using custom view attributes requires that you declare a new
XML namespace for those attributes; and that compound views
combine multiple views into a custom component.
257
uNderstaNdiNG How
Android drAws Views
Before learning how to create custom views, you should understand how Android
draws the display. As you learned earlier in the book, the Android UI is arranged
in a hierarchy. This hierarchy consists of the system elements, such as the notifi-
cation bar and the navigation bar, and the current activity view hierarchy. When
an activity is brought to the foreground, the system requests the root node of
that activity and then draws the view hierarchy on the display. You set the root
node of the activity when you call setContentView. The activity layout consumes
everything between the notification and action bars at the top and the navigation
bar at the bottom (if present).
The region of the display that is being drawn is flagged as invalid. Anything that
intersects with the invalid region needs to be redrawn. The system does this when
it draws an activity, but you can force this to happen by calling invalidate() on
a view. Once the view has been drawn, it is marked as valid.
Drawing takes place in two passes. In the first pass, the root of the hierarchy is
asked to measure itself. The root then measures each of its child views. Each child
view, in turn, measures its child views. In this way, the size of each view in the
view hierarchy is measured. At each level, a parent view may give its child views a
specific size or may request that the child set its own size.
Once the measuring phase is complete, the system performs the layout of the
view hierarchy. It walks down the layout tree in a predetermined order, drawing
each view onto a bitmap. Parent views are drawn first, and then the child views
are drawn on top of them. Once layout is complete, the drawing system draws the
bitmap to the screen to display it to the user.
Creating your custom UI components starts with extending a view class. You can
extend the base View class for maximum configurability, or you can start with one
of the existing view classes and add the functionality you need. Which choice you
should make depends on your application needs. The basic implementation is the
same either way: extend the view and override the appropriate methods, adding
your customized code along the way.
This only applies to static views or low-performance 2D graphics. If you want to
create 3D graphics or complex animations, you should extend SurfaceView or use
either RenderScript or OpenGL. See Chapter 11 for more details on advanced graphics.
To learn how to create custom views, you’re going to create a simple custom
view that displays two lines in the shape of a cross (Figure 10.1). To start, create a
new class called CrossView that extends the View class:
Note that the constructor takes both a Context and an AttributeSet object.
The Context provides the interface to the application resources and system ser-
vices that are needed to properly inflate the view and attach it to your activity.
The AttributeSet is required to pass XML parameters to your view. You’ll learn
more about this when you learn how to create custom XML attributes. You should
generally call through to the superclass in your overridden methods to perform
the initial setup of your view. With this done, there are two basic methods you will
likely want to override: onMeasure and onDraw.
oNmeasure
The onMeasure method is called by the system to determine the size of the view and
its children. It’s passed two integers that are actually MeasureSpecs. A MeasureSpec
is just a combination of a mode flag and an integer size value. It’s implemented as
an integer to reduce unneeded object allocations. The mode tells the view how it
should calculate its size. These are the possible modes:
J UNSPECIFIED. The parent view has placed no constraints on this view; it
can be any size it wants.
J AT_MOST. The view can be any size less than or equal to the MeasureSpec size.
J EXACTLY. The view will be exactly the MeasureSpec size regardless of what
it requests.
When you create a custom view and override the onMeasure method, it is your
job to properly handle each of these cases. In addition, the measuring contract
dictates that you call the setMeasuredDimensions method with the determined
integer size values. If you fail to do this, an IllegalStateException will be thrown.
Because the pixel density of devices varies, you must calculate the actual
pixel value using the display density.
If the mode is set to EXACTLY, then the input size will be used. If the mode
is set to AT_MOST, then the smaller of the DEFAULT_SIZE and the input size
will be used. Otherwise, the DEFAULT_SIZE for the view will be used.
When you compile your Android application, the SDK doesn’t just blindly
copy your application resources into the APK. Instead, it compiles them into
an efficient binary format to reduce their size and improve lookup perfor-
mance. To access the resources at runtime, you use a Resources object. You
retrieve a Resources object by calling getResources() from your application
context. The Resources object provides methods that take the compiled inte-
ger IDs for your resources and return the resource with the proper type.
oNdraW
The onDraw method is called when the view should draw its content. It is passed
a Canvas object, which holds the underlying bitmap for the view. The Canvas pro-
vides methods to do the basic drawing operations you use to build your view, and
it performs those drawing operations on its internal bitmap.
To perform the actual drawing, you can call one of the Canvas draw methods or
use a drawing primitive. Android provides several drawing primitives to construct
your UI: rectangles, ovals, paths, text, and bitmaps. You will also need a Paint object
to hold the styling that will be applied to the drawing. It handles things like color
and text size.
3. To make your drawing code simpler, scale the canvas based on the size of
the view. Doing this allows you to draw using simple floats between 0 and 1
without carrying around dimensions:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int scale = getWidth();
canvas.scale(scale, scale);
canvas.restore()
}
4. To draw the two lines of the cross, you’ll use the drawLines method of Canvas:
float[] mPoints = {
0.5f, 0f, 0.5f, 1f,
0f, 0.5f, 1f, 0.5f};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int scale = getWidth();
The drawLines method takes an array containing the lines to draw (two
x-coordinates and two y-coordinates per line endpoint) and a Paint object
that it uses to draw the lines on the canvas. By scaling the canvas, you are
able to specify all your dimensions using fractional float values.
When using custom views in your layouts, you generally use the class name
as the element tag name. However, this won’t work if your custom view is
an inner class of another Java class, because the required $ character is not
valid in Android’s XML layout. For example, if you were to make CrossView
an inner class of CustomViewsActivity, then the layout would not compile. In
that case, you would need to use the class attribute to set the fully qualified
view name:
<view
class=”com.example.CustomViewsActivity$CrossView”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”/>
note that this uses the lowercase (view) rather than the capitalized (View)
element. This signifies that it’s a generic view and that the class definition
can be found in the class attribute.
When using custom views in XML, you must use the full package name to
inform the system which view class should be inflated.
7. Run the application, and you should see a cross shape similar to the one
in Figure 10.1.
Now that you have your custom view, you’ll want to make it configurable to use
throughout your UI. Adding methods to the class for setting attributes is standard
Java practice, but how do you add attributes in the XML layout? To accomplish
that, you’ll first need to declare the attributes, then add a new namespace to your
XML layouts, and finally handle the AttributeSet object that gets passed to your
custom views constructor.
The first thing to note is that the <declare-styleable> element has a name
attribute. You use this to reference the styles in your code. Each custom attribute
is declared using an <attr> element. The <attr> element itself has two attributes:
name and format. The attribute name is used to reference custom attributes in XML.
The attribute format is the data type. In this example, one of the default system
attributes is used. In that case, you don’t need to declare the format, as it is already
defined by Android.
Every attribute with a format can be declared only once. This example works
because it uses the existing android:color format. If you tried to use a different
format, the project would not build. If you want to reuse the same attribute for
multiple custom styles, declare it under the <resources> tag and include a format;
then declare it inside each <declare-styleable> element without a format. Here
is an example:
You can create custom attributes with predefined values that are similar to the
built-in attributes like wrap_content and match_parent. To do that, you declare
the values using <enum> or <flag> elements:
<attr name=”enum_attr”>
<enum name=”value1” value=”1” />
<enum name=”value2” value=”2” />
</attr>
<attr name=”flag_attr”>
<flag name=”flag1” value=”0x01” />
<flag name=”flag2” value=”0x02” />
</attr>
This namespace declares that all attributes that begin with the keyword android:
can be found in the android package. To use custom attributes, you declare a
new namespace with a new package. This prevents your custom attributes from
colliding with system attributes that may be defined in later versions of Android.
Add a new namespace for the CrossView attributes:
This creates three crosses that are arranged vertically in a row. The second cross
is rotated 30 degrees and is blue. The third cross is rotated 45 degrees and is yellow.
The AttributeSet object is passed to your view by the system when it is instan-
tiated. This object contains all the attributes declared by the XML layout, but it
stores them in a more efficient binary format. Use it to retrieve the attribute values
and set them on your view.
2. Add a rotation field and update the onDraw method to rotate the canvas:
float mRotation;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
int scale = getWidth();
canvas.scale(scale, scale);
canvas.rotate(mRotation);
canvas.drawLines(mPoints, mPaint);
canvas.restore();
}
3. Add two new setters on the view. These are called by the constructor:
public void setColor(int color) {
mPaint.setColor(color);
}
public void setRotation(float degrees) {
mRotation = degrees;
}
Now when you run the app, you should see three crosses with different colors
and rotations (Figure 10.2).
Creating a custom view by extending the View class gives you the most control over
your custom view. However, you will often need something simpler, such as adding
functionality to an existing view. It is much easier to create a custom component
by extending one of the built-in Android views and expanding its functionality. By
leveraging the built-in view code, you can focus on adding enhanced functionality.
A simple way to do this is by creating a new view that combines several existing
views. This is called a compound component.
1. Create a new layout file named toggle_text.xml, and place it in the res/
layout/ folder:
Note that you are passing in this as the parent ViewGroup for the inflated
layout.
3. Add the custom functionality for your compound view. Add a listener for
changes in the selected state of the toggle button, and set the enabled state
of the EditText accordingly:
public class ToggleText extends LinearLayout
p implements OnCheckedChangeListener {
EditText mTextView;
public ToggleText(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.
p getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.toggle_text,
p this);
You can now use the new compound component in layouts, and the toggle
button will change the enabled state of the EditText (Figure 10.3).
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”ToggleButton” />
<EditText
android:id=”@+id/edit_text”
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:layout_weight=”1” >
<requestFocus />
</EditText>
</merge>
Now when you view the ToggleButton in the Hierarchy Viewer, the unneces-
sary LinearLayout will be gone (Figure 10.5).
Creating custom views gives you greater control over the look and functionality of
your application. Android allows you to extend the built-in view classes, leverag-
ing the existing drawing code while adding your own functionality. And by adding
custom attributes, you can use your new views in XML layout files for easy UI
development. In this chapter, you learned that
J You create a completely custom view by extending View and overloading
the onMeasure and onDraw methods.
J You use custom attributes of a view by declaring the XML namespace for
those attributes.
J Compound components let you easily build custom views out of existing
components but that you must remember to merge the layout with your
ViewGroup.
WrApping Up 279
11
creAting
ADVAncED
grApHics
The Android view framework is convenient for creat-
ing complex layouts. However, this convenience comes
at the cost of performance. When performance is critical,
Android provides several more-robust graphics capabilities with
increasing levels of difficulty. In this chapter, you will learn that
the SurfaceView and TextureView classes use the standard Canvas
object combined with a separate rendering thread to achieve bet-
ter performance than standard views; that the new RenderScript
framework can be used to create architecture-independent graph-
ics rendering; and that OpenGL is available for serious graphics
work and games.
281
usiNG cAnVAs
imPlemeNtiNG surFaCeVieW
The SurfaceView exists outside the normal view hierarchy. It actually exists behind
the normal window and is made visible by punching a hole through the view layout
in your app. The SurfaceView can then be updated independently of the rest of
your views without waiting for the UI thread.
To use a SurfaceView, you’ll need to create a new view that extends the SurfaceView
class. In addition, you should implement the SurfaceView.Callback interface and
provide implementations of the required callbacks:
3. Now implement the callbacks. The first, surfaceCreated, is called when the
surface view is ready to be used. You should start your drawing code here:
draWiNG to a surFaCeVieW
Unlike a normal view, all drawing to a SurfaceView takes place on a separate thread.
To draw on a SurfaceView, you must call the lockCanvas method of SurfaceHolder,
which returns a Canvas object. The lockCanvas method prevents the SurfaceView from
updating the underlying surface until you call the corresponding unlockCanvasAndPost
method. This eliminates the need for synchronization around writing to the surface
(though you still need to synchronize fields between your threads). You should wrap
all your drawing to a SurfaceView in lockCanvas and unlockCanvasAndPost blocks.
1. Create a new class named DrawingThread that extends Thread and calls
the onDraw method of the view. This thread will run every 20 milliseconds
(for 50 fps) and will update an angle field for the rotation of the triangle.
Remember to synchronize the drawing:
private class DrawingThread extends Thread {
boolean keepRunning = true;
@Override
public void run() {
Canvas c;
while (keepRunning) {
c = null;
try {
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
mAngle += 1;
onDraw(c);
}
} finally {
if (c != null)
mSurfaceHolder.unlockCanvasAndPost(c);
}
// Run the draw loop at 50 fps
try {
2. Add some fields to your view for the RGB colors, the triangle, and the new thread:
DrawingThread mThread;
int mRed = 0;
int mGreen = 0;
int mBlue = 127;
float[] mVertices = new float[6];
int[] mColors = {
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
Paint mPaint = new Paint();
float mAngle = 0;
float mCenterX = 0;
float mCenterY = 0;
public ExampleSurfaceView(Context context) {
super(context);
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
mThread = new DrawingThread();
mPaint.setColor(0xFFFFFFFF);
Paint.setStyle(Paint.Style.FILL);
}
The triangle is defined using lines, called vertices. The triangle will be filled
with color based on the mColors array.
The vertices define the edges of the triangle. The center values define the
pivot point around which the triangle will rotate.
5. Override the onDraw method of your view. Update the color and draw the
triangle:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRGB(mRed, mGreen, mBlue);
canvas.rotate(mAngle, mCenterX, mCenterY);
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, 6,
p mVertices, 0, null, 0, mColors, 0, null, 0, 0, mPaint);
}
6. Implement the onTouchEvent method and update the color as you slide
your finger across the screen. Note the synchronization on the changing
of the color values:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
synchronized(mSurfaceHolder) {
mRed = (int) (255*event.getX()/getWidth());
mGreen = (int) (255*event.getY()/getHeight());
}
return true;
}
return super.onTouchEvent(event);
}
Run the example and you should see a slowly rotating triangle (Figure 11.1).
Sliding your finger around the screen will change the background color.
The SurfaceView class lets you improve performance by moving the drawing onto a separate thread. But this
comes with some significant drawbacks. In particular, because SurfaceView exists outside the normal view
system, it can’t be transformed the way a normal view can. You can’t move, scale, or rotate a surface view.
In addition, SurfaceView doesn’t support transparency effects using setAlpha.
To address these shortcomings, Android 4.0 introduced TextureView. A texture view is essentially the same as
a surface view, but it behaves as a normal view and supports normal view operations. You can use a texture
view to display a content stream such as video camera preview, while also transforming it using the View API.
TextureView requires hardware acceleration and, because it is more flexible than SurfaceView, incurs a perfor-
mance hit. You would not want to use it for running a full-screen game, for example. However, if you are devel-
oping on Android 4.0 and need to transform a high-performance canvas view, consider using TextureView.
#pragma version(1)
#pragma rs java_package_name(com.example);
#include “rs_graphics.rsh”
// Background color is a 4-part float
float4 bgColor;
// Triangle mesh
rs_mesh gTriangle;
// Rotation float
float gRotation;
void init() {
// Initialize background color to black
bgColor = (float4) { 0.0f, 0.0f, 0.0f, 1.0f };
gRotation = 0.0f;
}
This code should be saved in a file called example.rs in your Android project’s
src/ directory. The first two lines declare the RenderScript version and the Java
package that contains the Java code that will use the RenderScript. A graphics
library, rs_graphics.rsh, is included. The init() and root() methods are special
to RenderScript. init is called when the script is first loaded. The root function
is like the main function in a standard C application; it will be called each time the
script runs. The number returned from root() requests the interval in milliseconds
at which the script should be called. Here, it is requested that the code be called
every 20 milliseconds. It does not guarantee that the code will be called that often,
however, only that the system will attempt to call the script that often.
2. Initialize the RenderScript Java objects. You first must create a RenderScriptGL
object, passing it a SurfaceConfig. The RenderScriptGL object ties the output
of the RenderScript to the display via the root() method:
public class ExampleView extends RSSurfaceView {
private RenderScriptGL mRS;
private ScriptC_example mScript;
public ExampleView(Context context) {
super(context);
final RenderScriptGL.SurfaceConfig sc =
p new SurfaceConfig();
mRS = createRenderScriptGL(sc);
mScript = new ScriptC_example(mRS, getResources(),
p R.raw.example);
buildTriangle();
Once the RenderScriptGL object is created, you can instantiate the ScriptC
object that contains the interface to your RenderScript file. Finally, you must
call the bindRootScript method to set your RenderScript as the handler
for calls to render the surface. You will implement the buildTriangle()
method shortly.
3. Create a simple activity that uses your new view as its content:
public class RenderScriptExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new ExampleView(this));
}
}
4. The reflected Java API lets you set variables in your RenderScript using
auto-generated setters. A setter for the bgColor variable has already been
created and can be used to change the background color. Add some inter-
activity by updating the graphics when you touch the screen. Override the
onTouchEvent method, and call the set_bgColor method:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float x = event.getX()/getWidth();
float y = event.getY()/getHeight();
Float4 color = new Float4(x, y, 0.5f, 1.0f);
5. Finally, create the buildTriangle() method, which will define the triangle
that your RenderScript will draw:
public void buildTriangle() {
Mesh.TriangleMeshBuilder triangles =
p new Mesh.TriangleMeshBuilder(mRS, 2,
p Mesh.TriangleMeshBuilder.COLOR);
triangles.addVertex(0.f, -75.0f);
triangles.addVertex(-150.0f, 75.0f);
triangles.addVertex(150.0f, 75.0f);
triangles.addTriangle(0, 1, 2);
Mesh mesh = triangles.create(true);
mScript.set_gTriangle(mesh);
}
When you run this, you will see a slowly rotating triangle, just as in the
SurfaceView example. The background color will change as you slide your finger
across the screen. Each call to the set_bgColor method updates the bgColor vari-
able. The root() method is called every 20 milliseconds and updates the back-
ground color and the triangle. It also updates the rotation angle by 1 degree. Try it
out by loading the app on a device and sliding your finger across the screen.
This simple example only scratches the surface of the RenderScript API. In addi-
tion to advanced graphics, RenderScript supports high-performance computing
using the compute APIs. These APIs are primarily found in the rs_cl.rsh header file.
Android provides full support for hardware-rendered graphics using the OpenGL
ES 1.0/1.1 and OpenGL ES 2.0 standards. OpenGL APIs can be called through the
Java framework and through the Native Development Kit (NDK). The Java frame-
work provides an easy-to-use API but suffers from a small performance hit. For
full accelerated graphics support, or to port existing graphics code, the NDK offers
the best solution. However, this is outside the scope of this book.
oPeNGl BasiCs
A full explanation of OpenGL graphics would consume an entire book. However, a
small example should give you a taste of the Java framework APIs that are available.
Creating OpenGL graphics requires two classes: GLSurfaceView and GLSurfaceView
.Renderer. The GLSurfaceView class is similar to SurfaceView and provides the glue
between OpenGL-rendered graphics and Android’s standard view framework. This
class provides a separate rendering thread that generates graphics independently
of the UI thread. In addition, GLSurfaceView provides some debugging tools that
assist in tracking down errors in rendering code.
Typically, you will extend GLSurfaceView and override the onTouchEvent
method to provide interactivity with your custom graphics. Here is an example
that simply paints the screen black. This example will be compatible with the
OpenGL ES 1.0 standard:
This is an OpenGL call that clears the specified buffers, which are referenced
via their bit values. Here the color and depth buffers are cleared, making
the screen black.
Here the view port is set to entirely fill the view dimensions.
You should remember to call the appropriate life cycle methods on GLSurfaceView
as your activity runs; OpenGL rendering can be very intensive, and this ensures that
your app does not consume resources when it doesn’t need them.
Run this example, and you’ll see that it just paints the screen black on each
frame. This isn’t very interesting, so in the next section you’ll add some graphics.
draWiNG GraPhiCs
You can easily re-create the example from the RenderScript section to rotate a
triangle and set the background color as you slide your finger across the screen.
1. Update the ExampleRenderer, and add a new setColor method with member
variables for color:
private float mRed;
private float mGreen;
private float mBlue;
public void setColor(float red, float green, float blue) {
mRed = red;
mGreen = green;
mBlue = blue;
}
2. Add member variables to draw a triangle, and initialize its vertices in the
onSurfaceCreated method:
WrApping Up 301
12
locAlizAtion AnD
AccessiBility
Creating a successful Android application requires
that you reach the largest audience possible. To that
end, you should work from the beginning to make your app
language agnostic and accessible to people with disabilities.
Localization will help you launch your app in more countries,
expanding your potential market to the whole world. Accessibil-
ity will also expand your target market, while helping those with
special requirements utilize the next generation of computing. In
this chapter, you will learn that using the strings.xml file allows
you to create multiple copies of your app strings in different lan-
guages; that you can define plural strings that present different text
based on the input number; that Android UI elements that have
no text should include a content description for screen-reading
software; and that accessibility events are sent by views, enabling
the accessibility service to notify the user.
303
mAking Your aPP AVAilABle
iN multiple lAnguAges
In Chapter 3, you learned about application resources and how Android uses folder
structure to separate resources for different device configurations at runtime. This
is very helpful in building apps that support multiple screen sizes. This same
mechanism allows you to localize your app with alternate strings and resources
for each locale.
The app_name resource remains the same, so you don’t need to create a
translated version. Android will select resources from both files.
4. Run the app as you normally would, and you should see the standard Hello
World app. Now change the emulator language to Spanish by choosing Set-
tings > Language and Input > Language > Spanish. Rerun the app, and the
displayed string is now in Spanish.
You are not limited to using language for localization; you can also create alternate
resources for specific mobile country codes (MCC) and mobile network codes (MNC).
Using these qualifiers allows you to create resources for specific geographic regions.
tip: the mCC and mNC qualifiers take precedence over the
language qualifiers. make sure you understand resource loading
precedence, and set up your resource folders appropriately.
As you can see, when the quantity “one” is used, the string uses the singular
“planet.” When any other number is used, it becomes “planets.” The full list of
plural types is listed in Table 12.1.
QuaNtitY Value n
Note: the plurals resources will only be selected in cases where the
language requires it. in other cases, the default will be used. For example,
in english, you would say “zero items,” “one item,” or “two items.”
the zero and two cases use the same plural. android will ignore the zero
quantity even if you create one.
ng
J Always include default resources in your app. If you fail to include them,
your app will crash when running in an unsupported locale.
J Always define user-visible strings in the strings.xml file. Use the default
strings.xml when you start developing your application. You can add
translated versions later.
J Use quantity strings to handle any plural strings in your app.
J Design your layouts to be as flexible as possible to accommodate languages
that may have different space requirements.
J Test your application in the alternative languages you support, and check for
any formatting errors. Consider creating alternative resources for those cases.
Run the example on the emulator, and use the navigation buttons to cycle
through the buttons. Notice that if you press just the up and down buttons, you
cannot reach the button on the right. To fix this, add the nextFocus attributes:
Now when you use the down and up keys to navigate, you will cycle through
all the buttons.
CoNteNt desCriPtioN
Beyond making your UI navigable by d-pad, you should strive to make it usable
by users with impaired vision. Vision-impaired users will not be able to navigate
your UI solely via a touchscreen interface. To work around this, users with vision
disabilities rely on screen readers to speak the UI elements as they slide their fin-
gers over the display. The screen reader reads the text of the view that the user is
<ImageView
android:id=”@+id/image”
android:src=”@drawable/example_drawable”
android:contentDescription=”@string/description” />
When accessibility features are enabled, users who touch the image will hear
the contents of the android:contentDescription attribute read aloud. By slid-
ing a finger across the screen, visually impaired users can navigate and use your
application with just spoken text.
aCCessiBilitY eVeNts
Accessibility features are provided by Android’s accessibility service. For the service
to work, all views need to implement the AccessibilityEventSource interface and
emit AccessibilityEvents when the state of the view changes. These events are
used by the AccessibilityService to provide users with awareness of events in
the UI. Changes in text, changes in focus state, and changes in click state are all
examples of events. The View class and its descendants already send Accessibility
Events at the appropriate times. However, when you implement a custom view,
you may want to generate AccessibilityEvents yourself, to handle any new func-
tionality the base class may be missing.
Accessibility events include properties that describe the event and its source.
If you extend a built-in view class, the base view will supply these properties for
you. Use the sendAccessibilityEvent method to send the event with the default
properties:
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
And if you need to change or add event properties, implement the dispatchPopulate
AccessibilityEvent method to adjust the event properties:
The Android documentation contains the full list of accessibility events that
you should be emitting. Take care to test your app using accessibility features to
verify that you are properly serving users with disabilities.
Localizing your app is required if you want to expand your market reach into other
countries. And ensuring that your app is accessible not only expands your potential
market, it helps people with disabilities make better use of technology. By following
Android best practices, you can achieve both with minimal effort. In this chapter,
you learned that
J You should use Android’s resource qualifiers to provide translated strings
for your app.
J You should always provide default resources; otherwise, your app may crash.
J You should add focus and content description attributes to your views to
make them navigable by users with impaired vision.
J When creating a custom view, you should make sure you emit the proper
accessibility events so that the accessibility service can properly handle
UI events.
As a final wrap up, I want to encourage you to take any knowledge you’ve gained
here and use it to create something awesome. Throughout this text, I’ve tried to
show you how to build fluid, beautiful software. It’s not always easy, but your users
will thank you for the effort. If you do find this book helpful, and you go on to
create an Android application, please let me know via Twitter (@jasonostrander).
I love trying new apps and would love to see what you’ve come up with. Good luck!
WrApping Up 315
index
316 index
resources, 12–13 background tasks. See also GridView, 206
responsiveness, 27 TimeTracker app layout, 206–207
AppWidgetProvider class, 197–200 Activity.runonUIThread ListView, 206–207
onDeleted method, 197 method, 68 RemoveViewsFactory, 208–210
onDisabled method, 197 AsyncTask class, 69–70 service, 208–210
onEnabled method, 197 Handler class, 66 StackView, 206
onReceive method, 197 handlers and message queues, colors
onUpdate method, 197, 201 66–68 placing in res/values folder, 13
AppWidgetProviderInfo XML file, Looper class, 66 transparent, 194
191–195, 209–210 message queue, 68 compound components
autoAdvanceViewID, 194 resetTimer method, 68 creating, 274–276
configure, 194 sendEmptyMessage method, 67 versus custom views, 274
icon, 194 stopTimer method, 68 optimizing layouts, 277–278
initialLayout, 194 ball, animating, 232–234 ToggleButton with EditText, 276
label, 194 bitmaps, using, 127 configuration changes, handling, 62
minHeight, 194 Boolean buttons, creating, 119 confirm dialog, creating, 90–91
minResizeHeight, 194 build target, setting, 5 confirmation dialog, creating, 93
minResizeWidth, 194 Button constant values, placing, 13
minWidth, 194 adding below TextView, 25 context menus
previewImage, 194 using, 128 creating, 100–101
resizeMode, 194 view object, 37 long pressing, 94
updatePeriodMillis, 194 button states, 95 counter.xml file, using with
assets/ folder, 9 buttons animation, 241–242
AsyncTask class, 69–70. See also tasks creating for TimeTracker app, 118 CrossView
attributes in linear layout, 42–43 changing color of, 273
adding to custom views, 267–273 in relative layout, 46 rotating, 273
AttributeSet object, 271 CursorLoader example, 185
crosses, 270 custom attributes
CrossView, 267, 269–270, 273 C adding to custom views, 267–273
declaring, 267–269 callbacks, receiving, 64 AttributeSet object, 271
enums, 269 canvas crosses, 270
flags, 269 described, 22 CrossView, 267, 269–270, 273
namespace, 269 drawing to SurfaceView, 282 declaring, 267–269
predefined values, 268 implementing SurfaceView, 282 enums, 269
rotating canvas, 272 rotating, 272 flags, 269
using in code, 271–273 TextureView class, 288 namespace, 269
ViewGroup, 269 circles, making for ball, 232–234 predefined values, 268
in XML, 269–270 click handling, avoiding anonymous rotating canvas, 272
AttributeSet object, using, 271–273 classes, 93 using in code, 271–273
AVDs (Android Virtual Devices), 26 clock-flipping animation ViewGroup, 269
creating, 6–7 animateDigit function, 243–244 in XML, 269–270
emulated, 26 creating, 240–245 custom views. See also views
graphics stack, 26 setting time, 242 accessing resources, 263
sliding animations, 244–245 activity for display of, 265
triggering, 242 versus compound components, 274
B collection widget. See also views; creating, 259–260
Back button, 19, 61–62 widgets CrossView, 265
back stack, 61–63 161 AdapterViewFlipper, 206 custom attributes, 267–273
index 317
custom views (continued) dip (density-independent pixel), 81 F
drawLines method, 265 disk I/O, detecting with StrictMode,
inner classes, 266 64–65 fill parent, 38
onDraw method, 263–266 display, drawing, 258 fillAfter attribute, using with
onMeasure method, 260–262 dp (density-independent pixels), 38, animations, 239
Paint object, 263–266 80–81 focus events, triggering, 95
Draw 9-Patch tool, using, 30, 82 form widgets, availability of, 37.
drawable animations. See also See also widgets
D animations forms
data binding creating, 232–234 Boolean buttons, 119
SimpleAdapter, 181 ImageView, 233 buttons, 118
making visible, 232 EditText, 112–113
SimpleCursorAdapter, 181
stop() and start(), 233 fillViewPort attribute, 121–123
DDMS (Dalvik Debug Monitor
Server), xi, 29 drawables managing settings, 120
Button class, 128 ScrollView container, 120–121
debug mode, detecting, 65
debug signing key, detecting, 65 StateListDrawable, 128 simplifying text entry, 115–118
default.properties item, 9 using, 128–129 spinners, 119
drawing display, 258 TextView, 112–115
density-independent pixels (dp), 38,
80–81 DrawingThread class, using with fragments
detail_item.xml layout, creating, 141 SurfaceView, 284 adding, 160
developer tools drawLines method, using, 265 adding to layouts, 154
downloading, 4 DummyTabFactory, using, 177 adding to TimeTracker app, 156–159
Draw 9-Patch, 30 back stack, 161
graphical layout editor, 22–25 features, 153
layoutopt, 30
E modifying, 160
Monkey, 31 Eclipse IDE, x, 4, 9 no-argument constructor, 156
onCreate activity callback, 155
device configuration edit_task.xml layout
onDestroy activity callback, 155
changes, 62 adding button to, 118–119
onPause activity callback, 155
editor, 24 creating, 117–118
onResume activity callback, 155
dialogs EditText class
onStart activity callback, 155
confirm, 90–91 combining with ToggleButton, 276
onStop activity callback, 155
confirmation, 93 flags, 116
creating, 89 inputType attribute, 116
removing, 160
DialogFragment, 91 using, 112–114 transactions, 159–160
FrameLayout container, using with
fragments, 91 email client, opening, 14
pre-Android 3.0, 90 emulator, 6, 8, 26 views, 41
digit.xml layout, using with event callbacks, 92 functions, adding via action bar, 171
animation, 240 event handling, 92
dimension units focus events, 95 G
in (inches), 81 key events, 95
dip (density-independent pixel), 81 long presses, 94 gen/ folder, 9
dp (density-independent pixel), 81 main thread, 92 GestureDetector class
mm (millimeters), 81 onClickListener, 93 onTouchEvent method, 225
pt (points), 81 screen taps, 93 using, 225–226
px (pixels), 81 Example project gestures
sp (scaled pixel), 81 creating, 4 customizing, 228
dimensions, placing in res/values project structure, 9 detecting thread violations,
folder, 13 running on phone, 8 225–226
318 index
GestureDetector class, 225–226 AVD (Android Virtual Device), 6–7 drawing into views, 129
pinch-to-zoom, 227–228 Build Target option, 5 fitCenter scale type, 125
ScaleGestureDetector class, creating, 4–8 fitEnd scale type, 125
226–228 drawable folders, 12 fitStart scale type, 125
getQuantityString method, using in main.xml file, 16 fitXY scale type, 125
localization, 308 package name, 5 ImageView and resources, 124–126
Google APIs website, 130 properties, 5 loading, 125
Google Maps, adding permission running, 5 matrix scale type, 125
for, 131 strings.xml file, 12 scaleType attribute, 126
graphical layout editor, 22–25 “Hello World!” text, displaying, 218 scaling, 125
Canvas, 22 Hierarchy Viewer, xi ImageView, for drawable
Configuration drop-down menu, 22 child LinearLayout, 28 animations, 233
Outline, 23 DDMS Devices pane, 29 IME (input editor), changing, 115
Package Explorer pane, 24 debugging nested LinearLayouts, 29 in (inches), 81
Palette, 23 FrameLayout, 28 inches (in), 81
tabs, 23 Layout View, 28 <include> tag
graphics left sidebar, 28 in detail page, 141–143
drawing, 296–300 LinearLayout, 28 examples, 140–141
projection matrix, 297–298 PhoneWindow, 28 layout attribute, 140
touch logic, 299–300 Tree Overview, 28 ls, 140–143
updateAngle() method, 298–299 Tree View, 28 inflation, explained, 97
gravity attribute, using with views, using, 27 input editor (IME), changing, 115
39–40 Holo theme, using, 152 Interpolator, ValueAnimator
GridLayout container Home button, 19 class, 246
adding space, 50 home screen interpolators
margins and padding, 50 app launcher icons, 14 accelerate, 238
Spaces, 50–51 grid layout, 196 bounce, 238
versus TableLayout, 50 options menu, 20 decelerate, 238
using with views, 48–51 replacement of, 15 overshoot, 238
GridView collection view, 206 widgets, 14 using with animations, 238
ISO 639-1 language codes, 304
ISO 3166-1-alpha-2 region code, 304
H I
Handler class, using with background icons
tasks, 66 creating with Android Asset J
hardware acceleration, adding, 252 Studio, 31 Java perspective, opening in Eclipse, 4
hardware buttons declaring in manifest, 11
alternative actions, 21 quick-launch, 14
Back, 19 image resources, creating, 31. See also K
Home, 19 resources key events, 95
Menu, 20 images
Search, 21 bitmaps, 127
hardware features, declaring in center scale type, 125 L
manifest, 11 centerCrop scale type, 125 labels, declaring in manifest, 11
Hello World app centerInside scale type, 125 language-specific layouts,
Android Device Chooser, 6–7 displaying, 124–129 providing, 306
Android emulator, 6, 8 drawables, 128–129
index 319
layout containers lists maps, adding to applications, 130–135
FrameLayout, 41 binding data to, 54–56 MapView, using, 130–132
GridLayout, 48–51 displaying, 52–56 margins and padding, 38
LinearLayout, 42–45 row layout, 53–54 match_parent, 38
RelativeLayout, 45–47 XML layout, 53–54 MCC (mobile country codes), 305
TableLayout, 41–42 ListView Menu button, 20, 96
ViewGroups, 37 attributes, 53 MenuInflater class, 97
layout resource qualifiers, 77 collection view, 206–207 menus. See also action bar
layout_gravity attribute, using, 40 defining, 53 assigning icons, 97
layout_margin attribute, using with entries attribute, 54 callbacks, 98–100
views, 39 using, 52 context, 94, 100–101
layout_weight example, 43 Loader helper class, using, 56 creating, 96
LayoutInflater class, 97 loaders layout inflation, 97
layoutopt tool, using, 30 CursorLoader, 185 layouts, 96–98
layouts using, 184–185 submenus, 97
adding fragments to, 154 localization <merge> tag
choosing dimensions for, 38 example, 305 using, 144–146
defaults, 52 formatting, 306–308 using with ToggleButton, 278
flexibility, 83 getQuantityString method, 308 millimeters (mm), 81
<include> tag, 140–143 ISO 639-1 language codes, 304 mm (millimeters), 81
inflating, 56 ISO 3166-1-alpha-2 region code, 304 MNC (mobile network codes), 305
life cycle, 154–159 language-specific layouts, 306 Monkey tool, features of, 31
margins and padding, 38 layouts, 308 MotionEvent object
<merge> tag, 144–146 MCC (mobile country codes), 305 actions, 219–224
nesting, 144 MNC (mobile network codes), 305 using, 219–224
optimizing, 30 overview, 304–306
TextView example, 154–155 plurals, 306–308
three buttons, 310–311 quantity strings, 308 N
views in, 17 resources, 304–306, 308 navigation
ViewStub class, 146–147 retrieving strings, 308 accessibility, 309–312
LayoutTransition class, using, 254 string substitution, 306–308 action bar, 172–174
LinearLayout container strings, 308 list mode, 174
orientation setting, 42 testing, 308 TabWidget interface, 175–178
using with views, 42 tips, 308 ViewPager behavior, 178
LinearLayout container type, 16–17 user-visible strings, 308 ViewPager class, 178–180
debugging, 29 long presses, 94 navigation mode, setting, 174
in Hierarchy Viewer, 28 Looper class, using with background network I/O, detecting with
list adapters, loading data into, 56 tasks, 66 StrictMode, 64–65
list navigation mode nextFocus attributes, using for
data binding, 174 accessibility, 311–312
OnNavigationListener, 174 M notification tray, accessing, 15
SpinnerAdapter, 174 main thread Notification.Builder class, 88
list position, saving, 62 avoiding blocking, 66 notifications
ListActivity, using, 52 in event handling, 92 creating, 86
ListAdapter explained, 61 dialogs, 89–91
overriding getView method, 54 manifest entry, using for activities, 57 NotificationManager, 87
using, 54 map key, registering for, 130 options, 88
MapActivity, extending, 132 parameters, 86
320 index
PendingIntent, 86–87 pinch-to-zoom gesture, available width, 77
setLatestEventInfo method, 88 implementing, 227–228 precedence, 78–79
status bar, 85–88 pixel density, variations, 80 screen orientation, 77
toasts, 84–85 pixels (px), 81 screen pixel density, 77
points (pt), 81 screen size, 77, 79
project structure SmallestWidth, 77
O AndroidManifest.xml item, 9 resources. See also image resources
ObjectAnimator class, using with assets/ item, 9 accessing at runtime, 263
property animations, 247–248 default.properties item, 9 referencing, 17
OnClickInterface, using with screen gen/ folder, 9 res/values/ folder, contents of, 13
taps, 93 res/ folder, 9 R.java file, 18–19
OnClickListener interface, 71, 93 src/ folder, 9, 12–13 rotating canvas, 272
onDraw method, using with custom projection matrix, setting, 297–298
views, 263–266 properties, setting, 5
onMeasure method, using with property animations. See also S
custom views, 260–262 animations scaled pixel (sp), 81
onSaveInstanceState, using with AnimatorSet object, 249–250 ScaleGestureDetector class, 226–228
activities, 62 LayoutTransition class, 254 scaling, using Draw 9-Patch tool for, 30
onTouchEvent method ObjectAnimator class, 247–248 screen configuration, determining, 83
GestureDetector class, 225 Property class, 249 screen orientation, 77
implementing, 216–219 ValueAnimator class, 246–248 screen pixel density, 77
updating, 221 ViewPropertyAnimator class, 253 screen sizes, 77, 79
using with SurfaceView, 287 in XML, 250–251 Draw 9-Patch tool, 82–83
OpenGL pt (points), 81 density-independent pixels (dp),
1.0 standard, 294 px (pixels), 81 80–81, 83
activity, 295–296 resource qualifiers, 76–80
drawing graphics, 296–300 screen taps, 93
example, 294–296
R button-click actions, 93
GLSurfaceView, 294 RelativeLayout container, 45–47 confirmation dialog, 93
onDrawFrame, 295 child views, 46 OnClickInterface, 93
onSurfaceChanged, 295 using, 29 onClickListener, 93
overview, 294 XMLAttributes, 47 screenshots, taking, 29
options menu functionality, 96 RemoteViews class, using, 201–203 ScrollView container
Outline, described, 23 RemoveViewsFactory, using with avoiding use of ListView in, 120
widgets, 208–209 fillViewPort attribute, 121–123
RenderScript language, 289–293 using, 120–123
P buildTriangle() method, 293 wrapping LinearLayout in, 121
Package Explorer pane, 24 downloading, 290 Search button, 21
package name, entering, 5 Java API, 291–293 settings, managing, 120
padding and margins, 38 RSSurfaceView class, 291 ShareActionProvider class, 171
padding attribute, using with views, 39 sample file, 289–291 SimpleAdapter, using in data
paging-style interface, using, 180 syntax, 289–290 binding, 181
Paint object, using with custom TriangleMeshBuilder, 293 SimpleCursorAdapter, using in data
views, 263–266 res/ folder, 9, 12–13 binding, 181
Palette, described, 23 res/layout folder, contents of, 12 sliding animations, creating, 244–245.
permissions, declaring in manifest, 11 resource qualifiers, 76–77 See also animations
phone dialer, opening, 14 API version, 77 sp (scaled pixel), 81
available height, 77
index 321
spinners, creating, 119 T AppWidgetProviderInfo XML file,
src/ folder, 9 191–195
StackView collection view, 206 tab layout, implementing, 175–177 Boolean buttons, 118
startAnimation method, using, 238 tabbed interface, creating, 172–173 button presses, 71–72
state table layout, example of, 41 buttons, 118
re-creating for activities, 62 TableLayout container clearing tasks to, 99–100
saving, 62 versus GridLayout, 50 clock-flipping animation, 240–245
StateListDrawable, using, 128 using with views, 41–42 confirm dialog, 90–91
status bar notifications, creating, TableRow container, using with convenience methods, 67–68
85–88 views, 41 creating, 36
StrictMode TabWidget interface, using, 175–178 detail page, 141–143
declaring, 64–65 task_detail.xml, opening, 142 edit_task.xml layout, 117–118
detecting thread violations, 65 TaskListFragment class, creating, 156 EditText, 113–114
disabling, 65 tasks. See also AsyncTask class fragments, 156–159
enabling, 65 back stacks, 62 implementing, 102–107
explained, 64 creating, 62 <include> tag, 141–143
string substitution, using in grouping activities into, 61–63 IntentFilter method, 106
localization, 306–307 switching between, 62 layout for list view, 53–54
strings text, zooming, 225–226 linear layout, 44–45
placing in res/values folder, 13 text entry, simplifying, 115–118 manifest entry for activities, 57
referencing, 19 TextureView class versus notification code, 106
retrieving in localization, 308 SurfaceView, 288 OnClickListener interface, 71
strings.xml file, contents of, 12 TextView, 17 overriding onClick method, 71
styles. See also themes animating, 238–239 Reset button, 72
adding to TimeTracker app, in clock-flipping animation, 240 simplifying text entry, 115–118
150–152 dragging onto layout, 25 Start/Stop button, 71–72
attributes, 149 using, 112–114 styles, 150–152
defaults, 149 using with fragment, 154–155 TextView, 113–114
defining, 148 visibility states, 240 timer layout, 159
inheriting, 150 themes, 152. See also styles timer update, 106
<item> elements, 149 threads, running in background, 66 timerStopped method, 105
placing in res/values folder, 13 three-button layout, using for TimerWidgetProvider class,
RedText, 148–149 accessibility, 310–311 197–200
styles.xml file, creating, 150 time_row-xml layout file, creating, tracking time intervals, 66–67
submenus, opening, 97 53–54 updateTime method, 105
sub-views, displaying in linear TimeListAdapter class, creating, 54 widget, 191
fashion, 17. See also views timer, stopping and resetting, toast notification, creating, 84–85
SurfaceView 102–107 ToggleButton
drawing to, 283–288 timer_appwidget_info.xml file, combining with EditText, 276
DrawingThread class, 284 creating, 191 <merge> tag, 278
implementing, 282–283 timer_widget.xml layout, creating, using Hierarchy Viewer with, 277
onTouchEvent method, 287 194–195 tools
rotating triangle, 288 TimerFragment, creating, 156–158 downloading, 4
versus TextureView class, 288 TimerWidgetProvider class, creating, Draw 9-Patch, 30
system attributes, prefix for, 38 197–200 graphical layout editor, 22–25
TimeTracker app. See also layoutopt, 30
background tasks Monkey, 31
adapter, 183–184
322 index
touch events view transparency, changing, 236–237 W
MotionEvent object, 219–224 ViewGroup
multi-, 219–223 layout containers, 37 websites
onDraw method, 222 using in custom attributes, 269 ActionBarSherlock library, 169
onTouchEvent method, 216–219, 221 ViewHolder pattern, using, 182–184 AppWidgetProviderInfo XML file,
touch logic, implementing, 299–300 View.Inflate method, 97 191–195
touchscreen device, including in ViewPager class, using, 178–180 displaying, 130–135
manifest, 11 ViewPropertyAnimator class, enabling plugins, 134
TranslateAnimation example, 236 using, 253 Google APIs, 130
triangle, drawing, 296–297 views. See also collection widget; overriding URLs, 134–135
TriangleMeshBuilder, using, 293 custom views; sub-views; RenderScript API, 290
TypeEvaluator, ValueAnimator class, 246 XML view widget template, 190
Adapter class, 182–183 WebView class
adding space around, 38 Flash support, 133
U arrangement of, 37 INTERNET permission, 133
UI thread data binding, 181 using, 133–134
dragging and dropping, 22–25 WebSettings object, 133
avoiding blocking, 66
in event handling, 92 drawing, 258 Widget Preview application, 195
explained, 61 fill parent, 38 widget size, calculating, 196
FrameLayout container, 41 widget_background.xml drawable, 194
updateAngle() method,
implementing, 298 gravity attribute, 39–40 widgets, 14. See also collection
UpdateWidgetTime method, GridLayout container, 48–51 widget; form widgets
implementing, 202 height and width, 38 app template, 190
inflating, 97 AppWidgetProvider class, 197–200
in layouts, 17 configuration activity, 203–205
V LinearLayout container, 42 declaring, 191
loaders, 184–185 described, 190
ValueAnimator class
loading data into, 181–185 layout, 192, 194–195
Interpolator, 246
match_parent, 38 RemoveViews class, 201–203
TypeEvaluator, 246
RelativeLayout container, 45–47 UpdateWidgetTime method, 202
using with property animations, wrap_content, 38
246–248 remote, 201–203
view animations. See also animations setting for activities, 18
TableLayout container, 41–42
alpha option, 235
TableRow container, 41
X
examples, 236–237
rotate option, 235
TextView, 17 XML layout, 16–17
scale option, 235
visibility states, 147 converting to View objects, 56
wrap_content, 38 LinearLayout container type, 16–17
translate option, 235
TranslateAnimation, 236
ViewStub layout, using, 146–147 setting for activities, 60
View attributes
virtual devices, 26 XML layout file, landscape version, 77
accessing, 37 creating, 6–7 XML view, switching to, 23. See also
form of, 38 emulated, 26 views
graphics stack, 26 xmlns:android attribute, 16
layout_margin, 39
padding, 39
visibility states
applying to drawable
specifying, 37–38
animations, 232 Z
view hierarchy, 41
View.GONE, 147 zooming text, 225–226
View objects
View.INVISIBLE, 147
Button, 37
View.VISIBLE, 147
hierarchy of, 37
index 323
V413HAV