C
C
C
Contents
ontents
ontents
ontents
1. Starting Android Development --- 2
2. Activities And Intents --- 41
3. User Interface Layouts and Controls --- 59
4. List Views and SQLLite --- 98
5. Services in Android --- 137
6. Content Providers ---155
7. App Organization ---186
8. Advance UI Components --- 200
9. Animation and Graphics --- 245
Chapter
Chapter
Welcome
Welcome
Welcome
Welcome
to the world of Android Application Development! In this book, we'll explore how to write apps for Android devices using Eclipse and the Android SDK. As of August 2012, Android devices held 70% of the market share of all smartphones, and sales of March tablets are increasing as well. There has never been an explosion of personal computing devices like the one we're seeing now. The opportunities for developers in this market are tremendous.But to take advantage of these opportunities, certain knowledge and skills are required. That's what this book is all about: to provide you with the basic skills you'll need to develop compelling and well written Android applications. With just a bit of effort on your part, you'll be well on your way to developing the next great app! The world will beat a path to your door! You'll be famous! Rich beyond your wildest imaginings!
OK, back to the real world. We can't promise fame or wealth, but we can promise that if you read this book and complete the exercises at the end of each chapter, you'll be able to develop applications that can be sold on Google Play and reach a wider audience than any desktop application would ever reach.
What
What
What
What you
you
you
you need
need
need
need to
to
to
to know
know
know
know
In this book, we assume that you have some experience with Object Oriented Programming using the Java programming language. The Android SDK is written in Java, and the applications we'll be writing in this book will be (for the most part) in Java as well. You don't need to be an expert, but you will need to be familiar with creating classes and objects, as well as the basic principles of object oriented design: inheritance, encapsulation, polymorphism, and so on.
What
What
What
What you
you
you
you need
need
need
need to
to
to
to have
have
have
have
The Android development tools and Eclipse software run on Windows, OS X, and Linux. You will need a development computer running one of these operating systems. If you will be developing on a Mac, make sure that it has an Intel processor.
The easiest way to develop apps for the Android platform is to do so within Eclipse: an Integrated Development Environment that is particularly well suited for working with Java. In the next section, we'll install Eclipse and the Android SDK and ADT (Android Development Tools) plugin. After that, we'll walk through the process of keeping the tools updated. Then we'll create an AVD (Android Virtual Device: a template for running an
application in the emulator), and write and run our first application.
We'll end this chapter by introducing the basic unit of app execution in Android: the Activity. We'll talk about what activities are as well as their life cycle: how and when they are created, move in and out of view, and destroyed. Understanding the Activity lifecycle is an essential first step in the journey to becoming an Android developer.
At the end of each chapter are exercises to help you understand the concepts presented. Solutions and explanations for all of the exercises are provided, but you should attempt them first without looking up the answer. Remember that in programming, there is almost always more than one way to solve a particular problem. Your way may even be better than ours, but the only way to learn to write good code is to practice writing good code!
1.1
1.1
1.1
1.1 Installing
Installing
Installing
Installing the
the
the
the tools
tools
tools
tools
1.1.1 Java JDK
The first step in preparing for the installation is to make sure that you have the Java Developer Kit (JDK) installed. You can verify the installation of the JDK by typing the following at the command prompt or in a terminal window:
javac -version
If the JDK is installed properly, you should see the version number of the java compiler displayed, for example:
javac 1.6.0_33
If the above command doesn't respond, you will need to install the JDK. Navigate your browser to http://www.oracle.com/technetwork/java/javase/downloads/index.html, and install the version of the SDK appropriate for your operating system. As of this writing, there are some issues still being reported with developing using the Android SDK with JDK 7, so for an initial install, we suggest JDK 6. Here is the JDK 6 installation section of the web page:
Note that you will need the full JDK (not just the JRE), so click the Download button on the left shown in the image above.
If you already have JDK 7 installed, you should not have any issues if you stick with the Eclipse IDE. If you later decide to use a third party IDE, you may need to adjust the settings within that product to use only the JDK 6 platform.
If the version of the JDK on your computer is earlier than 1.6, you will need to install at least 1.6 (JDK 6) to proceed. When you are able to issue the javac -version command and get a response like the one shown above, the JDK is properly installed.
1.1.2 Eclipse
In this book, we will use the Eclipse IDE to develop our projects. It is free, relatively stable, and easy to use. Setting it up for Android development can be a challenge, but once properly installed, it should give you very few problems.
Navigate to http://www.eclipse.org/downloads/ . Select your operating system as shown here:
For Android development, choose either the Eclipse Classic version, the Eclipse IDE for Java EE Developers version, or the Eclipse for Mobile Developers version. (The Classic version is fine for most uses. The Mobile Developers version adds support for the C language.)
Download the archive and extract it. Copy the eclipse folder to your home folder (or a folder that you own). When finished, make sure Eclipse is properly installed by running the executable inside the eclipse folder. If you want to make a shortcut to the executable, do so. Each time you run Eclipse, it will prompt you to enter a workspace. Choose a location (for
this first run, it doesn't matter where):
You can navigate to a particular folder by clicking on the Browse... button. Do not select the “Use this as the default and do not ask again” check box, because we always want the ability to change the workspace at need!
If this window is displayed, congratulations. Eclipse is installed properly.
1.1.3Android SDK
Once eclipse is properly installed, it's time to begin installing the Android SDK and plugin tools. Point your browser tohttp://developer.android.com/sdk/index.html. My advice is to read everything on this site before starting to install anything. There is a lot of good information here, but it is spread over several pages. It pays to get a good overview of the process before pushing the button!
These pages are also tailored to your operating system, so if you're not installing on a Mac, some of the instructions may be different that what is shown here. Again, read the
information on this site before doing anything.
When you've read all the information, click on the Download button on the page:
You'll want to save this .zip file to a safe location on your computer. Extract the .zip file to a folder that you have rights to (a folder in your home directory), and note the location. We'll need this location to tell Eclipse where the SDK is.
1.1.4 Android ADT
Next, we'll install the ADT (Android Development Tools). This is a plugin for Eclipse that specifically targets the Android platform. Start Eclipse (again, any workspace location is fine), and navigate to
The Installation screen shown here will appear. Type “ https://dl-ssl.google.com/android/eclipse/” in the Work with: text box and hit the Enter key. You should see the Developer Tools listed as shown in the view below.
Select the main Developer Tools checkbox to choose all the development tools, and click Next to continue. Eclipse will calculate requirements and show the current installation status of the tools in the Details panel. Click Next, accept all the license agreements for the tools you are about to install, and click Finish.
The ADT will take a while to download, so now is a good time to go have lunch.
1.1.5 Binding to the Android SDK
After the ADT has been downloaded, you must tell Eclipse where to find the Android SDK. Fortunately, this is a very simple process: in Eclipse, navigate to the Eclipse | Preferences menu selection:
You will see the Preferences window. Select Android on the left of the window, and enter the location in which you extracted the SDK files:
(You can also navigate to the location where the files are stored by clicking the Browse... button.) Click OK at the bottom of the window. Note that the SDK Targets panel will not show installed targets until the SDK itself is updated. At this point, it's a good idea to restart Eclipse to make sure the ADT plugin is properly installed.
1.1.6 Running the SDK Update Process
The SDK we downsloaded and installed in the last step only contains the core package, which allows us to install the rest of the SDK packages. In the Eclipse menu, navigate to Window |
You will see the Android SDK Manager window shown below. At the top of the window, the location of the SDK files is shown; this will be the same location you entered in the previous step.
Android 2.3.3 SDK Platform and Google APIs, as well as the latest versions of the Android SDK Tools and Android SDK Platform-tools (under the Tools folder at the top of the listing). After making these selections, click on the Install packages... button at the bottom right of the window.
This process will take a considerable amount of time. It's a great time to take a break, but keep
an eye on the process, because you will be asked to accept license agreements, etc. before the actual installation process begins.
The installation should complete successfully. Once all the files are in place, restart Eclipse to make sure the installation is a success. If you have problems installing any component of the tools or Eclipse itself, make sure that:
� Eclipse is installed in a folder to which you have full rights (such as a subfolder of your home folder). If on Windows, do not attempt to install Eclipse for “all users,” as not all users will have rights on the installation target folder. This is probably the number one cause of installation problems with Eclipse.
� The Android SDK should also be installed in a folder to which you have full rights. I like to create a Developer folder in my home folder and then create both an Eclipse folder and an AndroidSDK folder inside of that. Download Eclipse to the Eclipse folder, and extract the Android SDK into the Android SDK folder, then bind the SDK to Eclipse using the process discussed above.
� Make sure you have installed the ADT before attempting to bind to the SDK or install updates to it. The ADT plugin adds the Android – specific menu items to the Eclipse application.
After installing Eclipse, the ADT plugin, and the SDK, we should test our installation by writing a short application and running it. It's traditional when learning a new language or platform API to start with a “Hello World” program. In fact, if we omit this step, we're likely to call down the wrath of the programming gods, so we'd better get to work!
1.2
1.2
1.2
1.2 Hello,
Hello,
Hello,
Hello, World!
World!
World!
World! –––– Creating
Creating the
Creating
Creating
the
the
the First
First
First
First App
App
App
App
If Eclipse is still running, exit. We're going to create a folder in which to put the Hello, World program, then restart eclipse with this new folder as the Workspace. Make a new folder for the examples and exercises called “EBook,” and then create a “Chapter1” folder inside it. Inside the Chapter1 folder, create a HelloWorld folder. Now we're ready to start up Eclipse. When the Workspace Launcher window appears, use the Browse... button to select the folder
you just created:
Click OK, and allow Eclipse to load. Click on the Go To Workbench icon.
There is currently no project in the HelloWorld folder, so we must create one. There are two ways to create a new Android project: we can click in the File | New... menu and select Android Application Project... or simply right-click on the empty Package Explorer panel, as shown here:
Fill in the text boxes as shown in the image above. The Package Name should be separated by
dots. For this ebook, we're using com.ebook., followed by the name of the application we're working on in all lowercase (here, helloworld). Since we've only installed one SDK platform (2.3.3), we should use that for both the Build SDK and the Minimum Required SDK. Also, to
keep things simple, we've unchecked the Create Custom Launcher Icon checkbox. When you are finished making these selections, click Next>. We will be using the higher versions of SDK as we move to other chapters later in the book.
In the next window, you will be prompted to create an Activity, and asked what kind of activity to create. For now, make the selections as shown:
Click Next> to continue.
In this window, the Activity Name will be the name of the class to create, and the Layout Name will become the name of the .xml file that describes the view. Make the changes as shown above, and click Finish.
The ADT plugin will generate the application files for us, using the choices we made in these three screens to fill out a basic application template. The first file that is usually displayed is a graphical representation of the app's layout.
Chapter 2 will discuss activities in some detail, but for now we need a basic overview of the concept.
1.2.1 What's an Activity?
We're going to start with a simple definition of an activity that we will build on (and modify) later. An Activity is a single unit of user interaction in an application. When the user views or enters information in an android app, they are doing so by using an activity.
The HelloActivity class was created for us in the src (source) folder by the ADT. Let's take a look:
We can see that HelloActivity inherits from a class called Activity, which is Android's base class for all activities. The Activity class contains several life cycle methods that we can override in our apps to handle behavior as the activity changes state during the life of a running app. Two of these methods are already overridden for us: onCreate and onCreateOptionsMenu. We'll look at the other life cycle methods in the next section.
The onCreate method is called when the activity is created. In this method, we have a chance to give some initial settings to the activity. In this case, after calling through to super, we are setting the content view (what will be displayed) to be the layout_hello.xml file.
The setContentView requires an int parameter. These int identifiers are created for us and placed in an auto-generated file called R.java. The R.java file is located in the gen folde
So, by passing R.layout.layout_hello to the setContentView method, we are really passing the int defined in the layout class within the R class:
We should never modify the R class ourselves: it will be updated by the ADT when we add or delete resources (such as user interface elements) in various files of the application. We should also never use the actual values of the ints in R (such as 0x7f030000): always refer to them by name (as in R.layout.layout_hello).
called. onCreate displays the layout_hello layout, and the app starts its run loop. Since we've defined no controls for the user to actually interact with, the app just loops (in this case) until it is closed.
But if we try to run the application now, we'll find that we're missing something. The app can't run because we haven't defined an Android Virtual Device (AVD) for it to run on in the Emulator. Oops! We'd better take care of that...
1.2.2 Creating an AVD
An AVD is an emulator image file. Each AVD we create specifies settings for the specific device we want to emulate. While it would be nice to have one of each physical device to test on, in single-developer environments or in small teams this is rarely within the budget.
The Android Virtual Device Manager window pops up (isn't the ADT helpful?):
Obviously, since you probably don't have any AVD's created yet, you won't see the list shown here. Click on the New... button, and create a 2.3.3 AVD with the options shown here:
Click on Create AVD to complete the process. The ADT is very smart... when we attempt to run an Android application and no actual device conforming to the target API level is
attached, the ADT goes through the list of virtual devices and attempts to find one that will run the app.
Close the Android Virtual Device Manager window. Congratulations... now that you have a layout, an activity, and an AVD to run it in, the app should run in the emulator!
1.2.4 Running Hello, World!
The “Run” button is located in the Eclipse button bar. When it is clicked on an app that has not been run before, it brings up the following dialog box:
Select “Android Application” and click OK. If you see a Console message like this one:
it indicates that the ADT plugin has lost the connection to the Android Debug Bridge. Don't worry, it happens. Eclipse is a very nice environment to work in, but it sometimes displays, shall we say, “strange” behavior. This problem is actually very often solved by restarting
This is the console giving “good” messages prior to the application launch:
Don't worry about the message that states that NSQuickDrawView has been deprecated, this is a known issue: Google has been promising to fix it for years.
If all goes well, the application will run in the emulator. You may have to “unlock” the phone before you see the app:
All this without writing a single line of code! Yes, it's pretty boring, but if you are able to run the app and see the above result in the emulator, you've actually accomplished quite a bit: Eclipse is installed with the ADT plugin, the SDK is installed and updated with the 2.3.3 Gingerbread API and platform tools, you have a working AVD, and everything is right with the world. Now we can begin learning how to write apps!
1.3
1.3
1.3
1.3 A
A
A
A Preview:
Preview:
Preview:
Preview: Handling
Handling
Handling
Handling Events
Events
Events
Events
HelloWorld is a great little app, but it doesn't do much. Let's add a button to the interface to allow the user to control when the message is displayed. As you'll see, this doesn't require much additional code at all.
Open the layout_hello.xml file. If the file is in xml view, switch to graphical view using the tab at the bottom of the window. Drag a button control from the Form Widgets panel to the interface, then move the text view control (that currently says Hello World) to a position above the button. The interface should look something like this:
Mak the highlighted changes to the file:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="110dp" android:text="@string/hello_world" android:visibility="invisible" tools:context=".HelloActivity"/> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:layout_centerHorizontal="true" android:layout_marginTop="44dp" android:onClick="showText" android:text="@string/button_text"/> </RelativeLayout>
We set the visibility attribute of the TextView control to “invisible” because we're going to control its visibility through the button. We've also added an attribute to the Button – android:onClick. This attribute tells the button to call the showText method (that we must define in the activity) when it is clicked. We've also altered the android:text attribute to point to a string resource named “button_text.”
While we could have entered a hard coded string in the android:text attribute, it is best to get into the habit early to use string resources instead of hard coded strings. Open up the res/values/strings.xml file and add the highlighted string resource in the xml view:
<resources>
<stringname="hello_world">Hello world!</string>
<stringname="menu_settings">Settings</string> <stringname="title_activity_hello">Hello</string> <stringname="button_text">Say Hello</string>
</resources>
When the layout sees @String/button_text in the text attribute of the button, it looks in the strings.xml file and replaces the text on the button with “Say Hello.”
Save your work, then open up the HelloActivity.java file. Add the showText method to the HelloActivity class, right below the onCreate() method:
package com.ebook.helloworld; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.widget.TextView;
public class HelloActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.layout_hello); }
public void showText(View view) {
TextView tv = (TextView) findViewById(R.id.textView1); tv.setVisibility(View.VISIBLE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.layout_hello, menu); return true;
} }
There are two ways to handle button clicks: we can use methods of the activity coupled with the onClick attribute (as shown here), or we can add a button handler to the activity directly in the onCreate() method. This method is simpler, but both have advantages, as we shall see later.
We've created the showText method. Notice that it takes one parameter: a View object. View is the root class of all layouts and controls in an Android app. The View that gets passed to this method is the control that was interacted with, in this case, the button. We've named this View view.
In the method, we obtain a TextView object (named tv) by using findViewById. This method takes an int parameter, which is the id of the TextView in the layout. We get this by looking at the textView1 property of the id class within the R class. Recall that we never use these int ids directly as ints. Here's how we can reference them using the values declared in R.java.
Finally, we set the Visibility attribute of the TextView to View.VISIBLE. There are three possible visibility states: VISIBLE, INVISIBLE, and GONE. These correspond to the attribute values visible, invisible, and gone in the layout xml. They are defined as an enum in the View class. Visible and Invisible are easy to understand, but Gone might require some explanation. When a control is set to have Gone visibility, not only is it invisible, but the space it occupies in the layout is potentially reclaimed by other controls in the layout. This reclamation behavior is especially useful when hiding controls in a list view.
Run the application and notice that the Hello world! Text now only appears after the button is clicked:
1.4
1.4
1.4
1.4 Hello,
Hello,
Hello,
Hello, Lifecycle!
Lifecycle!
Lifecycle!
Lifecycle!
informs us as the activity's state changes. We'll be building on the HelloActivity application (which is just really the template app provided by the ADT when it creates a new activity class).
1.4.1 The Activity Lifecycle
There are six states an activity can be in during its lifetime: Created, Started, Resumed, Paused, Stopped, and Destroyed. Each time an activity transitions from one of these states to another, a life cycle method is called: onCreate, onStart, onResume, onStop, and onDestroy. By overriding these methods (and a few others), we can respond to state changes by (for example) setting up control handlers, saving information, or killing threads that were started from within the activity (preventing memory leaks). An understanding of the state changes within the life of an activity is crucial to providing a user with a consistent experience.
C r e a t e d S ta r t e d R e s u m e d P a u s e d S to p p e d D e s t r o y e d O n C r e a t e () o n S t a rt ( ) o n R e s u m e () o n P a u s e () o n S t o p ( ) o n D e s t r o y ( ) o n R e s u m e () o n S t a r t () o n R e s t a r t ()
The chart above shows the various activity states and the methods that are called during the transitions from one state to another. The first method to be called is onCreate(). The Created state is not actually entered until onCreate() is finished. For example, if we try to refer to some
object that doesn't exist until the activity is in the Created state, the reference will fail.
Once an activity is in the Created state, onStart() and onResume() are called in very rapid succession. In both of these states the layout is visible, and we can refer to items within it. But activities do not remain in the started state for very long at all. Started is really a transitional state. We can override onStart() and onResume() to detect the transition from Created to Started (in the first case) and Started to Resumed (in the second).
The Paused state is entered whenever the activity is partially hidden. This usually happens when the activity calls another activity (for example, a second screen for user input). But if the second activity doesn't cover the whole screen or is partially transparent, the first activity will remain in the Paused state until the second activity is no longer visible. When this happens, the first activity will re-enter the Resumed state (calling onResume() on the way), and the second state will enter the Stopped state after its onStop() method is called.
Stopped is entered when the activity is not visible on the screen. If the activity is about to be destroyed, onDestroy() will be called to give us a last chance to save information. On the other hand, the activity might be restarted, in which case the state will transition to Started (calling onRestart() and onStart() in rapid succession) then Resumed (calling onResume()). You should memorize these state transitions and the callback methods associated with them.
1.4.2 Writing the App
Create a new folder inside the Chapter1 folder and name it HelloLifecycle. Start the Eclipse application, and point the Workspace to this new HelloLifecycle folder. Click OK, then select the Workbench icon to enter the new workspace.
Since this is a new workspace, again we have to create the application. Refer to the discussion in the HelloWorld app to create the HelloLifecycle app, with a Blank Activity named LifecycleActivity. In the New Blank Activity screen, use the following properties:
Click Finish, and when the ADT creates the project from the template, open the
LifecycleActivity.java file, located in the src folder (under the package name).
Now, make the highlighted changes in the source code for LifecycleActivity.java shown here: package com.ebook.hellolifecycle;
import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.Menu;
public class LifecycleActivity extends Activity {
private static final String logTag = "LifecycleActivity";
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
Log.i(logTag, "onCreate called");
setContentView(R.layout.layout_lifecycle); }
public void onStart() { super.onStart();
Log.i(logTag, "onStart called"); }
public void onRestart() { super.onRestart();
Log.i(logTag, "onRestart called"); }
public void onResume() { super.onResume();
Log.i(logTag, "onResume called"); }
public void onPause() { super.onPause();
Log.i(logTag, "onPause called"); }
super.onStop();
Log.i(logTag, "onStop called"); }
public void onDestroy () { super.onDestroy();
Log.i(logTag, "onDestroy called"); }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.layout_lifecycle, menu); return true;
} }
We've overridden all the lifecycle callback methods mentioned earlier. In each case, we've made sure to call through to the superclasses' callback method. This is very important: the runtime will throw an error if we forget to call through to super in these methods! All we're doing is logging out the fact that we're in each method, using the i (for information) method of the Log class. Log writes its output to an ADB (Android Debug Bridge) application called LogCat. The ADT adds LogCat to Eclipse, we can show the LogCat pane by first menuing to Window | Show View | Other... then selecting LogCat from the Android group:
LogCat will now appear in a tab at the bottom of the Eclipse screen.
Log's i method takes two String parameters, a TAG, and the string you want to log out. In this case, we've defined the tag as a constant (private static final) String: logTag. We use this tag for all the calls to Log.i.
1.4.3 Running Hello, Lifecycle!
When we run the program, we observe the times the callbacks are executed. When the activity starts, we get three log messages:
These three methods were called at 14 seconds after 1:45 p.m. The Activity was in the Created state from some time after 338 microseconds to 597 microseconds. We can also see that virtually no time was spent in the Started state, since onResume was also called at 597 microseconds.
Here, onPause is called, followed by onStop. Notice that the activity actually remains in a Paused state for as long as it is at least partially visible (over ½ second). Pressing the Home key does hide the app, however, so at 110 microseconds, the activity begins transitioning to the Stopped state. Activities in the stopped state are still in memory, just not displayed on the screen.
Now watch what happens when the app is resumed by touching its icon:
The activity is transitioning from a Stopped state to a Resumed state, but instead of calling onCreate, onRestart is called. Note the rapidity with which these three callbacks fire: all are called within the same microsecond!
Finally, observe the log when the Back button on the emulator is used to end the app:
This is similar to the Home key state transition, but in this case onDestroy is called, indicating
that the activity is about to be released and its memory will (eventually) be reclaimed by the system. Again, we see a bit more than ½ second elapsing between the calls to onPause and onStop. As we shall see later, onPause is the preferred place to save application state for a number of reasons, one of which is this ½ second gap.
Exercises
Exercises
Exercises
Exercises
1. Modify the HelloWorld app's layout xml file by setting the android:visibility property of the TextView to “gone.” Note the change in the graphical view of the layout file. Now run the app. What happens when the button is tapped? Why?
2. Modify the HelloWorld app so that it has a black background overall and yellow text in the TextView. The android:background attribute controls the background color or image for a view. The android:textColor attribute controls the color of the text in a TextView. A color can be specified in the form #RRGGBB, where RR, GG, and BB are the red, green, and blue values of the color in hexadecimal form. For example, #FFFF00 would result in a bright yellow color.
3. Write an Android app that has two buttons and a text view. The two buttons should read “One” and “Two.” When the user taps the “One” button, the text view should read “1”, and when the user taps the “Two” button, the text view should read “2”. Try to handle both buttons using only one method in the activity. (Hint: you can use each button's id to determine which button was tapped.)
4. Modify the HelloWorld app. Add a new string resource named button_clicked_text to the strings.xml file, and set the value of this string to “OK”. Add the following two lines to the top of the showText(View view) method:
Button btn = (Button) view;
btn.setText(R.string.button_clicked_text);
Run the app and observe the result. What's going on here?
5. Modify the HelloWorld app so that the first time the user clicks the button, the text appears and the text on the button changes to “OK.” The next time the button is tapped, the text view should become invisible, and the text on the button changes back to “Say Hello.” Use the text property of the button itself to determine what action should be taken.
Chapter
Chapter
Chapter
An
An
An
An
Android Activity is a set of files that specifies a user interface and handles user input. At the heart of every activity is a class definition. Most activity classes are subclasses of Activity, an SDK class that defines many attributes and lifecycle methods needed to write a typical user interface.In this chapter, we'll start with a simple app containing a single activity and extend it as we go along. We'll learn how to handle user input, and how to display a second activity using an intent. Working with activities is the heart of Android development: the concepts presented in this chapter will form the basis of everything we do later. So let's get started.
2.1
2.1
2.1
2.1 A
A
A
A New
New
New
New App
App
App
App
Start Eclipse (if its not open already) and create a new Android Application Project in a new workspace. Name the project “Activities” and give it the properties shown here:
(You can use any Build/Run SDK you wish; in the first few chapters of this book we use 2.3.3 because a majority of Android smart phones are still on this platform). Click Next, and follow through the screens to create a blank activity application. On the final screen, accept the
default settings, and click Finish.
When the template has been created, expand the Project Explorer so that the MainActivity.java file is displayed:
Double click the file to open it. Here it is, in all its glory: package
package package
packagecom.ebook.activities;com.ebook.activities;com.ebook.activities;com.ebook.activities;
import import import importandroid.os.Bundle; import import import importandroid.app.Activity; import import import importandroid.view.Menu; public public public
public classclassclassclassMainActivityextendsextendsextendsextendsActivity { @Override
public public public
public voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
super
setContentView(R.layout.activity_main); } @Override public public public
public booleanbooleanbooleanbooleanonCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return
return returnreturn truetruetruetrue; }
}
In the MainActivity class, two methods are defined: onCreate and onCreateOptionsMenu. These are both lifecycle methods of an activity, as we already know. The purpose of onCreate is to make initial settings in our activity, and to bind the activity to a layout. The activity_main.xml has already been created for us, and should already be open in a tab. Choose this tab, and change the view by clicking activity_main.xml at the bottom of the view:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" tools:context=".MainActivity"/> </RelativeLayout>
As we can see, two XML namespaces are defined: android and tools. The android: namespace defines all the controls we can use to build user interfaces along with their attributes, and the tools: namespace is used internally to map the activity to a layout context. (As of this writing, this is the only purpose of tools, but it is reserved for more uses in future updates.)
In the latest version of the SDK, the entire display is wrapped in a RelativeLayout container; in prior versions, this was a LinearLayout with a vertical orientation. Relative Layouts allow us to align controls relative to the edges and center of the layout; Linear Layouts “spring” controls to the top of the layout (if vertical) or the left of the layout (if horizontal). We'll explore layouts and controls in detail in Chapter 3.
For now, let's get rid of the TextView control by deleting it. Your layout file should now look like this:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
If you display the layout in graphical view (by clicking the Graphical Layout tab), you'll see the effect of this change:
We can switch between the graphical and xml views of a layout at any time. From now on, we'll show only the XML in this book, but you should switch back and forth often to check your progress.
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <RadioGroup android:id="@+id/radioGroup1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="77dp"> <RadioButton android:id="@+id/radio0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="RadioButton"/> <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RadioButton"/> <RadioButton android:id="@+id/radio2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="RadioButton"/> </RadioGroup> </RelativeLayout>
Right off the bat, we see some warning messages that we'll need to correct if we can. Each RadioButton has a hard-coded text, as we discussed in Chapter 1, it would be better if we used string attributes for these texts. Open the res/values/strings.xml file, and add the following three strings:
<stringname="radio1_text">Red</string> <stringname="radio2_text">Green</string> <stringname="radio3_text">Blue</string>
Save the file and return to the activity_main.xml file. Change the android:text attributes in the three RadioButtons to read:
android:text="@string/radio1_text"
android:text="@string/radio2_text"
android:text="@string/radio3_text"
Also delete the line “android:checked=”true” from the first RadioButton. That takes care of three of the warning messages. The fourth warning has to do with the fact that at this point, we have a top-level control (the relative layout) wrapping a top-level control (the radio group). This will fix itself in the next step, when we add a button control to the user interface. In graphical view, drag a button control to a position under the radio group. Once again, open strings.xml and add a text string for the button:
<stringname="button1_text">Change Color</string>
In the activity_main.xml file, change the android:text attribute of the button to
android:text="@string/button1_text"
Check the result in graphical view. If we run the app now, we can change the radio buttons and click the button, but nothing will happen (because the button control has nohandler). Edit the onCreate method in MainActivity.java to read:
public public public
public voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super super supersuper.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final final
finalfinalButton button = (Button) findViewById(R.id.button1); button.setOnClickListener(newnewnewnewOnClickListener() {
public
publicpublicpublic voidvoidvoidvoidonClick(View view) {
RelativeLayout layout = (RelativeLayout) findViewById(R.id.main_container); RadioGroup choice = (RadioGroup) findViewById(R.id.radioGroup1);
RadioButton selection =
(RadioButton) choice.findViewById(choice.getCheckedRadioButtonId()); Log.i("Click Handler", String.format("%d", selection.getId()));
switch switch switch switch(selection.getId()) { case case case
caseR.id.radio0:
layout.setBackgroundColor(Color.argb(255, 255, 0, 0)); break break break break; case case case
caseR.id.radio1:
layout.setBackgroundColor(Color.argb(255, 0, 255, 0)); break break break break; case case case
caseR.id.radio2:
layout.setBackgroundColor(Color.argb(255, 0, 0, 255)); break
break break break;
} }
}); }
Now run the app. Note that selecting a radio button, then clicking the button will change the background color of the relative layout. (at this point, if you click the button before selecting a color, the app will throw an exception):
All of this code depends on being able to find the ids of the various controls in the interface. We instantiate a new Button object named button by finding the button control using its id (button1). Then we create a new onClickListener that contains the onClick method to bind to the button. Inside the listener, we create a RelativeLayout object (called layout) and a RadioGroup object (called choice). In each case, we find these controls by using their id's. This pattern is a recurring one in Android development.
In order to find the layout by id, we have to add an id attribute to the RelativeLayout control in activity_main.xml:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id android:id android:id
android:id===="@+id/main_container""@+id/main_container""@+id/main_container""@+id/main_container" android:layout_width="match_parent"
android:layout_height="match_parent">
After we have the RadioGroup object, we can get the currently selected radio button by calling the getCheckedRadioButtonId method of the choice object. This method is defined by the RadioGroup class. We store this radio button in a RadioButton object called selection. After that, it's a simple matter of comparing the selection object's id to the known radio button ids in our interface. This is done in the switch statement.
Each case sets the background color using the argb method of the Color class. We pass in the Alpha, Red, Green, and Blue values desired (hence the name argb).
I really can't stress enough the importance of understanding how ids work in the user interface. The id attribute of a layout control is always an integer defined in R.java, but we should never use the actual integer value. Instead, we use the name assigned in the R file: R.id.radio0, for example. Without an understanding of finding and using ids, user interfaces are impossible to create.
At this point, we have an amusing little app that doesn't really do much. Let's look a little deeper though, and learn something new about intents.
2.2
2.2
2.2
2.2 Intents
Intents
Intents
Intents
Open the AndroidManifest.xml file in xml view. The contents of this file are shown below:
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.ebook.activities"
android:versionCode="1"
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="15"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/title_activity_main"> <intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter>
</activity> </application> </manifest>
The manifest file is a repository of information about an application. In fact, we could call the manifest file the glue that holds an app together. We'll be talking about manifest files and their entries for most of the book, but for now, let's concentrate on the information between the <application … > and </application> elements.
We can see that there is a single <activity...> element. The android:name attributes of this activity is “.MainActivity” which corresponds to our MainActivity class. The dot is used in conjuntion with the manifest's package property (com.ebook.activities) to resolve the namespace of the MainActivity class: com.ebook.activities.MainActivity. The android:label string resource is also identified: we can change this value in the strings file. This is the label that will be displayed in the title bar of the activity.
The next entry we see defines an intent-filter for this activity. An intent-filter for an activity informs the system what actions the activity will respond to. In this case, the intent-filter tells the system that this is the MAIN activity for the application, and it should respond to a system event triggered by tapping the app's LAUNCHER icon. There are many values we can specify in intent-filters to respond to various system events, and we shall explore these as we continue through the text.
All activities have an intent. The intent is the calling mechanism for an activity. It can simply specify the name of the activity to be called, or pass data between one activity and another. Intents are the glue that holds activities (and other application components) together. Each time we add a new activity to an application, we must register that activity and its intents within the application's manifest file.
Lets add a new activity to our application to see how the process works. We'll add a new button to our interfaces to bring up a new activity that gives detail about the color chosen. With the com.ebook.activities package selected in the Package Explorer, right – click and make the following selections:
Name the new class DetailActivity, and click the Browse... button of the Superclass text box to select the Activity class:
(type Activity in the search bar to find the class). Click Finish to create the new class. We're going to need a layout file for this new activity, in the res/layout folder, right – click and add a new Android XML file. Name the file layout_detail and choose the LinearLayout as shown:
Click Finish to create the new file.
We want the DetailActivity class to bind to this new layout file when it is created, so let's override the onCreate method in DetailActivity.java:
public
publicpublicpublic voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
super
supersuper.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); }
project. Choose Project | Clean... from the menu to do this.
To start with, we'll just go through the steps to display the new activity. Let's change the background color of the new layout to black in activity_detail.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background android:background android:background android:background===="#000000""#000000""#000000""#000000"> </LinearLayout>
Next, we'll need to add a new button to the activity_main.xml file (add this after the button1 control): <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/radioGroup1" android:layout_below="@+id/button1" android:layout_marginTop="22dp" android:text="@string/button2_text"/>
Since the button2_text string doesn't yet exist, add it to strings.xml:
<stringname="button2_text">Detail…</string>
… is the unicode ellipsis character (…). Now open MainActivity.java, and add the new button handler to the end of the onCreate method:
final final final
finalButton btnDetail = (Button) findViewById(R.id.button2); btnDetail.setOnClickListener(newnewnewnewOnClickListener() {
public
publicpublicpublic voidvoidvoidvoidonClick(View view) {
Intent detailIntent =newnewnewnewIntent(MainActivity.thisthisthisthis, DetailActivity.classclassclassclass); startActivity(detailIntent);
} });
The creation of the button handler itself should look familiar, but notice that the onClick method creates an intent. The Intent class is instantiated as detailIntent, and we use the Intent(thisClass, newClass) constructor to create this object. MainActivity.this is (of course, this class), and DetailActivity.class is the pointer to the next activity to be started by this intent. After creating the intent, we can pass it to the startActivity method.
Whew! We're almost there...
In order for the new activity to start, we must register it in the AndroidManifest file for this app (in the tabs, this is called Activities Manifest). Open the manifest file, and insert the following after the existing </activity> element:
<activity
android:name=".DetailActivity"
android:label="Color Detail"> </activity>
Notice that the DetailActivity class name must be preceded by a dot so that the manifest can resolve this activity to com.ebook.activities.DetailActivity. We're also hard-coding the label of the activity to “Color Detail” for simplicity. We really should create a new string in the strings.xml file for this, but that can wait.
If we run the app now, we can load the new activity by tapping the Detail... button.
Pressing the back button on the keypad returns to the MainActivity screen. There's not much to look at in the DetailActivity at this point, but we know it works up to this point!
Now let's see if we can get the DetailActivity to display the name of the color selected in the MainActivity.
Open the activity_detail.xml file and add a new TextView object with these properties:
<TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#FFFF00" android:textAppearance="?android:attr/textAppearanceMedium"/>
Since the background of this layout is black, we set a text color of yellow (#FFFF00) for the text. Now add the following code to the btnDetail handler in MainActivity.java:
final final final
finalButton btnDetail = (Button) findViewById(R.id.button2); btnDetail.setOnClickListener(newnewnewnewOnClickListener() {
public
publicpublicpublic voidvoidvoidvoidonClick(View view) {
RadioGroup choice = (RadioGroup) findViewById(R.id.radioGroup1); RadioButton selection = (RadioButton)
choice.findViewById(choice.getCheckedRadioButtonId()); String chosenColor ="None";
switch switch switch switch(selection.getId()) { case case case
caseR.id.radio0:
chosenColor ="Red"; break break break break; case case case
caseR.id.radio1:
chosenColor ="Green"; break break break break; case case case
caseR.id.radio2:
chosenColor ="Blue"; break break break break; }
Intent detailIntent =newnewnewnewIntent(MainActivity.thisthisthisthis, DetailActivity.classclassclassclass); detailIntent.putExtra("color", chosenColor);
startActivity(detailIntent); }
});
Much of this code is similar to the other button handler. We set up a string (chosenColor) to get the name of the color chosen in the switch statement, again depending on the radio button selected. But notice the new code in our detailIntent. We've added an extra to the intent. Here, we supply a name (it can be any string) and a value: the value is the string chosenColor that
we set in the switch. When the activity is started, the intent will carry this extra information! Now alter the onCreate method of DetailActivity.java:
public public public
public voidvoidvoidvoidonCreate(Bundle savedInstanceState) { super
super
supersuper.onCreate(savedInstanceState); setContentView(R.layout.activity_detail);
TextView tvColor = (TextView) findViewById(R.id.textView1); Bundle extras = getIntent().getExtras();
if if
ifif(extras !=nullnullnullnull) {
tvColor.setText(String.format("Color: %s", extras.getString("color"))); }
}
First, we find the TextView by its id. Then we get the extras within the intent as a Bundle object named extras. If extras isn't null, we know the color was passed in the intent, and we set the text property of tvColor to the value passed in the color extra. Pretty neat, huh? Let's see if it works...
name of the new color. As many extras as we want can be added to an intent to pass information around, and this is a good way to get information to something like a detail view. We've learned a good deal about simple activities and intents in this chapter, but there is still a great deal more to learn, especially about intents. In the next chapter, we'll explore layouts and user interface elements in detail, and learn how to make great looking interfaces. For now, watch the chapter videos and do the exercises to test your knowledge.
Exercises
Exercises
Exercises
Exercises
1. As mentioned in the text, there is a bug in the Activities app. If the user taps either button without first selecting a color, the app will error out. Fix this bug so that the Change Color button sets the color to Black (255, 0, 0, 0) and the Detail button reports Color: Black. 2. There is a good bit of common code in the two button handler methods. Factor out this code into a new method. You can also factor out the controls found by id in these methods to attributes of the MainActivity class. (There is more than one solution to this exercise. Try it, then compare your solution to the one provided in the answers).
3. Currently, the user must tap the back button to return to the MainActivity view. In the android documentation, can you find a way to add a button to the DetailActivity view that will close the DetailActivity and return to the MainActivity?
4. One way to complete exercise 3 is to create an intent in the DetailActivity that loads the MainActivity when startActivity is called on it. Implement this approach in the Activities app. Use the new button to return from the DetailActivity several times. Now tap the back button repeatedly until the app exits. Is this approach a valid one? Why or why not? 5. Add a new activity to the Activities app. This activity should be started and displayed
when the user taps a new button in the MainActivity screen. When the activity loads, it should have the same background color as MainActivity, with a text view showing the hexadecimal color value in argb format: #AARRGGBB. Name the activity ColorCodeActivity.
Chapter
In
In
In
In
this chapter, we'll begin looking at the objects that make up the Android User Interface: Layouts and Controls. In an Android application, the placement of controls such as buttons and text is done within a layout control. There are two basic types of layout (with variations): the linear layout and the absolute layout. Linear layouts position their controls according to a predefined structure, while absolute layouts allow positioning of controls anywhere on their surface by specifying the position of each control in relation to an edge or the center of the layout, or in relation to other controls.For the examples in this chapter, each type of layout has a corresponding project in the ebook's source repository. [link to each as they are brought up...] These examples are provided for your review, but you should work through the concepts presented in this chapter yourself before looking at our source code.
3.1
3.1
3.1
3.1 Linear
Linear
Linear
Linear Layouts
Layouts
Layouts
Layouts
Start a new Android Application project in Eclipse named LinearVertical. Our source is located there in the attached files. When the project has been created, open the activity_main.xml file and switch to .xml view. You will see the following text:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" tools:context=".MainActivity"/> </RelativeLayout>
Since the introduction of the Rev. 20 SDK Tools, the default layout wrapper for a new application is The Relative layout. We'll be discussing relative layouts in a bit. For now, delete all of the text in the .xml file, as we'll be starting from scratch.
Return to graphical view and drag a LinearLayout (Vertical) control from the Layouts folder in the palette to the interface:
(The above image shows the layout in the process of being dragged to the interface, as well as the appearance of the layout once it is released in the interface area.) In .xml view, the result looks like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"> </LinearLayout>
The layout manager in the SDK is fairly smart. Note that since this the first layout applied to the interface (and therefore will become the container for the interfaces in this activity), the namespace “android” has been applied, and the layout_width and layout_height have both
been set to “fill_parent.”
Return to graphical view, and drag a Large Text control from the Form Widgets folder in the palette to the linear layout. Notice that there is only one place you can position this control: the linear layout is constraining the positioning of controls according to its metrics. Here is the result in the xml view:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge"/> </LinearLayout>
The TextView is positioned within the layout. Note also that we've added an attribute to the LinearLayout: android:orientation=”vertical”. This forces the linear layout into vertical mode: the omission of this attribute in the current version of the SDK is an oversight on the part of the Android designers, but is easily fixed as shown here.
Within the new TextView, we see various attributes. Each component in a user interface has layout_width and layout_height attributes. In this case, the TextView is set to wrap its content both horizontally and vertically. The textAppearance attribute is set to ?android:attr/textAppearanceLarge, since this is a Large Text object. We can change this value to make a medium or small text object, or simply use the preset values in the graphical interface.
Of major importance in the attribute list is the android:id attribute. It is this value that we will bind to in order to find this particular TextView in code. The syntax @+id/textView1 tells the system to create a new id named textView1 and assign an integer value to it. If we open R.java, we will see this new value has been created:
R.java is located in the /gen folder under the package name. As the comment says, you should never modify this file. Also, never use the actual integer values assigned in R.java; always use the symbolic name (such as textView1).
R.java is automatically updated for us as well. For example, if we change the name of the TextView's id:
<TextView
android:id="@+id/helloTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge"/>
after we save the file, the field name in R.java is changed as well:
public public public
public public
publicpublic staticstaticstaticstatic finalfinalfinalfinal intintintinthelloTextView=0x7f070000; public
public
publicpublic staticstaticstaticstatic finalfinalfinalfinal intintintintmenu_settings=0x7f070001; }
This is important: it means we never have to make such changes in R.java ourselves, nor should we!
You've probably also noticed that there is a warning at the android:text line. The SDK will warn us when we apply a text string as a constant in the xml for a control. To silence this warning, we can use a string resource as discussed in chapter 2. For now, we'll delete the entire line, as we'll be setting the text later by clicking a button:
<TextView
android:id="@+id/helloTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"/>
Returning to graphical view, drag a button control to the linear layout. Once again, notice that the placement of the button is constrained by the layout: we can place the button above or below the textview, but not to the right or left of it. A vertical linear layout functions as a single column of controls, aligned to the left edge of the layout itself. Here is the xml view:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/helloTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge"/> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button"/> </LinearLayout>
view and add a new string as shown here:
<resources>
<stringname="app_name">LinearVertical</string> <stringname="hello_world">Hello world!</string> <stringname="button_text">Say Hi</string> <stringname="menu_settings">Settings</string>
<stringname="title_activity_main">MainActivity</string> </resources>
Save this file, and return to the activity_main.xml file. Change the android:text attribute of the button control to use this new string resource:
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_text"/>
Since the string is already defined in the resource file, we omit the + after the @ sign here. Save your work and return to graphical view:
Notice that the button is placed below the TextView, and the button's text has been set to the content of the string resource just defined.