Android Programming Guide for Beginners

January 27, 2017 | Author: Andra Gabriela | Category: N/A
Share Embed Donate


Short Description

Download Android Programming Guide for Beginners...

Description

1 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Contents

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

10. GPS and Sensors --------------------------------------------------------------------294

2 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 1 – Starting Android Development

3 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 you need to 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 you need to 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

4 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 Installing the 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:

5 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

6 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 7 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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! After clicking OK, You should see a window similar to this:

8 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 to http://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 9 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 Help | Install New Software... :

10 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The Installation screen shown here will appear. Type “https://dlssl.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.

11 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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. 12 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

13 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

(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 |

Android SDK Manager as shown

14 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

For the work we are going to do in the first part of the book, it is only necessary to install the 15 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 Hello, World! – Creating the First 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

16 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

Either way, the New Android App window is displayed:

17 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 18 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 the final window, we're going to make a few changes:

19 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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. Before we continue, let's take some time to go over the concept of an Android Activity.

20 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

Double-click on the HelloActivity.java file to open it in the editor:

21 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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). When the application starts, the HelloActivity will be instantiated, and onCreate will be

22 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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. To create an AVD, use the Eclipse menu to navigate to Window | AVD Manager:

23 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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

24 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 Eclipse. 25 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

26 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 A Preview: Handling 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:

Open the xml view of the file by clicking the layout_hello.xml tab at the bottom of the view. 27 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Mak the highlighted changes to the file:



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: HelloWorld

28 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Hello world! Settings Hello Say Hello

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);

29 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

}

@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:

30 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

1.4 Hello, Lifecycle! In this section, we're going to explore the activity lifecycle by writing a small application that 31 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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. O n C r e a t e ()

C re a t e d o n S t a rt ( )

S ta r t e d o n R e s u m e ()

Resum ed

o n S t a r t ()

o n P a u s e ()

o n R e s u m e ()

P aused

o n R e s t a r t ()

on S to p( )

S to p p e d

on D e s tr o y ( )

D e s t ro y e d

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 32 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

33 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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;

34 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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"); }

public void onStop() {

35 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

36 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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. If we press the Home key in the emulator, we see this:

37 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

38 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

39 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 2: Activities and Intents

40 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 A New 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 41 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 com.ebook.activities; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState);

42 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } }

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:

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: 43 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved





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. Drag a Radio Group from the Form Widgets palette to the view. Switch to the xml view: 44 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



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: Red Green Blue

45 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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: Change Color

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 no handler). Edit the onCreate method in MainActivity.java to read: public void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Button button = (Button) findViewById(R.id.button1); new OnClickListener() { button.setOnClickListener(new public void onClick(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 (selection.getId()) { case R.id.radio0: layout.setBackgroundColor(Color.argb(255, 255, 0, 0)); break break; case R.id.radio1: layout.setBackgroundColor(Color.argb(255, 0, 255, 0)); break break; case R.id.radio2: layout.setBackgroundColor(Color.argb(255, 0, 0, 255)); break break;

46 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

} } }); }

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):

47 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

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 Intents Open the AndroidManifest.xml file in xml view. The contents of this file are shown below:

48 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



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 and elements. We can see that there is a single 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. 49 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

50 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); }

If R.java has not yet created the id for the activity_detail file, you may have to clean the 51 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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: android:background=



Next, we'll need to add a new button to the activity_main.xml file (add this after the button1 control):

Since the button2_text string doesn't yet exist, add it to strings.xml: Detail…

… is the unicode ellipsis character (…). Now open MainActivity.java, and add the new button handler to the end of the onCreate method: final Button btnDetail = (Button) findViewById(R.id.button2); new OnClickListener() { btnDetail.setOnClickListener(new public void onClick(View view) { this class Intent detailIntent = new Intent(MainActivity.this this, DetailActivity.class class); 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. 52 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 element:

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! 53 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

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 Button btnDetail = (Button) findViewById(R.id.button2); new OnClickListener() { btnDetail.setOnClickListener(new public void onClick(View view) { RadioGroup choice = (RadioGroup) findViewById(R.id.radioGroup1); RadioButton selection = (RadioButton) choice.findViewById(choice.getCheckedRadioButtonId()); String chosenColor = "None"; switch (selection.getId()) { case R.id.radio0: chosenColor = "Red"; break break; case R.id.radio1: chosenColor = "Green"; break break; case R.id.radio2: chosenColor = "Blue"; break break; } this class this, DetailActivity.class class); Intent detailIntent = new Intent(MainActivity.this 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: 54 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); TextView tvColor = (TextView) findViewById(R.id.textView1); Bundle extras = getIntent().getExtras(); if (extras != null null) { 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...

We can go back and change the color on the first screen, then click Detail... again to see the 55 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

56 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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.

57 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 3: User Interface Layouts and Controls

58 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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 Linear 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:

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:

59 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

(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:

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

60 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

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:

61 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

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:

after we save the file, the field name in R.java is changed as well:

public static final class id {

62 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public static final int helloTextView=0x7f070000; public static final int menu_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:

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:

We should use a string resource for the button's text. Open /res/values/strings.xml

in xml

63 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

view and add a new string as shown here: LinearVertical Hello world! Say Hi Settings MainActivity

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:

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. 64 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

As an experiment, change the android:orientation of the LinearLayout to “horizontal” and return to graphical view:

Now the button is positioned to the right of the (currently empty) TextView, as specified by the metrics of a horizontal linear layout. Change the layout's orientation back to vertical. By the way, if you look at R.java now, you will notice that entries have been added for the button and the new string resource: /* AUTO-GENERATED FILE. DO NOT MODIFY. * * This class was automatically generated by the * aapt tool from the resource data it found. It * should not be modified by hand. */ package com.ebook.linearvertical; public final class R { public static final class attr { } public static final class drawable { public static final int ic_action_search=0x7f020000; public static final int ic_launcher=0x7f020001; } public static final class id { public static final int button1=0x7f070001; public static final int helloTextView=0x7f070000; public static final int menu_settings=0x7f070002; } public static final class layout { public static final int activity_main=0x7f030000;

} public static final class menu { public static final int activity_main=0x7f060000; }

65 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public static final class string { public static final int app_name=0x7f040000; public static final int button_text=0x7f040002; public static final int hello_world=0x7f040001; public static final int menu_settings=0x7f040003; public static final int title_activity_main=0x7f040004; } public static final class style { public static final int AppTheme=0x7f050000; } }

It is a simple matter to write a button handler to set the text in the TextView. In MainActivity.java, add the following method:

public void sayHi(View view) { TextView targetTextView = (TextView)findViewById(R.id.helloTextView); targetTextView.setText("Hi there!"); }

The parameter view (of type View) refers to the object whose handler this is: in this case the button. We can use the view parameter to alter the state of a control, get the value of a control's attributes, or even determine which control is being interacted with. The targetTextView variable is assigned an object of type TextView by using the id attribute of the TextView to find the particular text view whose text we wish to change. The findViewById method returns an object of type View: we must cast this to a TextView type in order to assign it to a new object of the TextView type. After we have the text view as an object, we can call the setText method on it, which will change the text. Running the app now and clicking the button results in the following display:

66 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Now switch the orientation of the LinearLayout to horizontal and re-run the application. Notice what happens when the button is clicked (before and after layouts are shown below):

The button jumps to the right to make room for the new text! This is because the text view is set to wrap its content (android:layout_width and android:layout_height are both set to “wrap_content”). When the button handler calls setText on the text view, the text view's width is updated to wrap the new text string. “Jumpy controls” are considered to contribute to a bad user experience generally, so this sort of behavior should be avoided. In the case of a horizontal layout, any controls whose width may change dynamically should be placed to the right of those with a constant width, or the layout_width should be stated as a constant in the attribute. More on this later. The same may be said of vertical layouts containing controls whose height may change during the course of user interaction. It is best to entirely avoid having controls that move, unless the user is expected to move the controls themselves, and it is clear that this is expected. LinearLayouts may be nested, for example we can place a horizontal linear layout inside a single row of a vertical linear layout. Here's an example in which we've added a horizontal layout below the existing controls, which contains two large TextView controls:

67 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

In the xml, the layout structure is quite evident:

68 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved





Using this technique, we can build up quite complex user interfaces using only linear layout containers. Sometimes we need access to a layout container itself to modify some attribute in code. Remember that to get to a control from a piece of code, we must be able to identify the control. We can do this easily by setting an id attribute for the layout itself:

Let's modify the button handler in MainActivity.java to make use of this information: public void sayHi(View view) {

69 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

LinearLayout mainLayout = (LinearLayout)findViewById(R.id.mainLayout); TextView targetTextView = (TextView)findViewById(R.id.helloTextView); mainLayout.setBackgroundColor(Color.CYAN); targetTextView.setText("Hi there!"); }

As shown above, we can find the id of any component of a layout in the same way, by calling findViewById and passing the value of the id of the view we wish to locate. Once we have a view object, we can access or modify any property of that view by calling the property's setter or getter. Here, we modify the background color of the layout in addition to setting new text in the TextView:

Linear layouts are useful for setting up simple user interfaces, but for full control of the position of controls, we must turn to a relative layout.

3.2 Relative Layouts Like linear layouts, relative layouts base the position of their controls on the metrics of the layout itself, but unlike linear layouts, the position of a control is aligned with or is an offset from any corner, edge, or the center of the layout. Controls may be aligned with other controls in the layout as well, as we shall see. This gives us considerable freedom to design our layouts, but there are a few quirks to working with relative layouts as well. Create a new android app using any API level (for this example I'm using 4.0.3 (Ice Cream Sandwich)). Accept the default template settings as usual. When the app is created, navigate 70 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

to the res/layout/activity_main.xml file if it is not already displayed. In the XML file, make the following changes, as they will add contrast to the layout:

Notice that the TextView object, in addition to a layout_width and a layout_height attribute, now has layout_centerHorizontal and layout_centerVertical attributes as well. These are example of relative layout attributes, of which there are many. Let's return to graphical view: I've zoomed in a bit to make things clearer. In this view, notice that when the TextView is selected, the relative layout attributes are shown to the upper right of the interface display.

These correspond exactly with the xml file's attribute names. Move the text view around on the layout and observe the changing attribute names and their settings. Also note that the 71 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

alignment attributes are displayed in a tooltip while the text view is being moved. If we move the text view to the center of anyedge of the layout, the layout attributes will be changed in such a way that there will be one parent edge attribute and one center attribute. These attributes and their values for the centers of each edge are shown in this table: Layout Placement

Attributes in graphical view

XML settings made

Top, Center

alignParentTop: true centerHorizontal: true

android:layout_alignParentTop=”true” android:layout_centerHorizontal=”true”

Bottom, Center

alignParentBottom: true centerHorizontal: true

android:layout_alignParentBottom=”true” android:layout_centerHorizontal=”true”

Left, Center

alignParentLeft: true centerVertical: true

android:layout_alignParentLeft=”true” android:layout_centerVertical=”true”

Right, Center

alignParentRight: true centerVertical: true

android:layout_alignParentRight=”true” android:layout_centerVertical=”true”

Moving the control to the corners results in these attributes being set:

Layout Placement

Attributes in graphical view

XML settings made

Top, Left

alignParentTop: true alignParentLeft: true

android:layout_alignParentTop=”true” android:layout_alignParentLeft=”true”

Top, Right

alignParentTop: true alignParentRight: true

android:layout_alignParentTop=”true” android:layout_alignParentRight=”true”

Bottom, Left

alignParentBottom: true alignParentLeft: true

android:layout_alignParentBottom=”true” android:layout_alignParentLeft=”true”

Bottom, Right

alignParentBottom: true alignParentRight: true

android:layout_alignParentBottom=”true” android:layout_alignParentRight=”true”

As long as we're dealing only with edges, centers, and corners, everything is very straightforward. Now, let's move the TextView to a position near (but not actually in) the upper left corner of the layout:

72 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The graphical view shows that the object is aligned with the top and the left sides of the layout. But if we examine the XML, we can see two new attributes:

These layout margin attributes are calculated from the edge of the control facing the nearest edge of the layout. In other words, layout_marginLeft=”68dp” means that the left edge of the control is 68 density – independent pixels from the left edge of the layout, and layout_marginTop=”66dp” means that the top edge of the control is 66 density – independent 73 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

pixels from the the top edge of the layout. Even though the alignment is still to the parent's left and top, a margin is applied to these alignment metrics to adjust the actual position of the control. Let's take a short detour at this point and talk about units of measure. [EDITOR: You may want to put this section (until END below) in a sidebar with the title Android Measurement Units.] There are a number of measurement units that can be applied to set positions, widths, and heights of controls. These are summarized in this chart: Unit

Description

in

Units are given in inches, measured by the physical size of the screen

mm

Units are given in millimeters, measured by the physical size of the screen

pt

Units are in points (1 point = 1/72 inch), measured by the physical size of the screen

px

Units are given in actual physical screen pixels

dp, dip

Units are given in density – independent pixels. One density – independent pixel is equal to one pixel at 160 dots per inch. Both dp and dip are accepted; dp is preferred.

sp

Units are given in scaled – independent pixels. One scaled – independent pixel is equal to one pixel at 160 dots per inch, but the measure is also scaled by the user's font size preference.

We can distil this information a bit: use sp for anything involving text, and use dp for everything else, unless actual screen pixel positions are important (such as in a game or other graphical application). In that rare case, use px px. When positioning a control in graphical view, the margin attributes are set according to the nearest layout edge. For example, if the text view is positioned near the lower right corner of the layout, layout_marginRight and layout_marginBottom are used:

74 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The arrows pointing from the control to the edges of the layout in graphical view will always indicate which margin attributes are being set in the xml file:

In the above image example, we know that layout_alignParentBottom, layout_alignParentRight, layout_marginBottom, and layout_marginRight are all being used. If the control is centered horizontally or vertically (but not both...) three attributes will be set: a layout_center... attribute, a layout_alignParent... attribute, and a layout_margin... attribute. In the following case, the text view is centered horizontally and offset a bit from the top of the layout:

Here is the corresponding xml:

We've already seen that changes in the graphical view are immediately set in the xml file. Changes in the xml are also reflected immediately in the graphical view. For example, if we change the xml file to read: 75 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



the graphical view is changed as well:

So far, we've looked at the attributes that are applied using only one control in a relative layout. Now let's see what happens when we add a second control. Center the first text view horizontally 30dp from the top of the layout, then drag a new Medium Text control to a

76 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

position below the existing text view: The new TextView has layout_centerHorizontal applied; this should not be surprising. But the new text is aligned not with the parent, but with the previous text object. In graphical view, we can see this described as “below: textView2,” in xml view, the two text views are listed like this:

A new attribute: layout_below is assigned with the id of the first text view to indicate that the 77 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

new text view is to be aligned vertically to the textView2 object. The layout_marginTop attribute in this case refers not to the distance between the top of the layout and the top of textView1, but to the distance between the top of textView1 and the bottom of textView2! If we move the medium text object so that its left edge is aligned with the left edge of textView2, the attributes in the xml file change to:

textView1 is still below textView2, but now the layout_centerHorizontal attribute has been replaced with layout_alignLeft=”@+id/textView2”. Note the distinction between layout_alignLeft and layout_alignParentLeft: when we align with the parent, a boolean is expected and the control will be aligned with the object that contains it. When we simply align, we are aligning to another control at the same hierarchical level, and the id of that control is expected. There are of course many attributes that can be applied to specify the alignment of one control with respect to another. The most important are: layout_below layout_above layout_toLeftOf layout_toRightOf layout_alignLeft layout_alignRight layout_alignTop layout_alignBottom For a complete discussion of layout attributes applicable to the relative layout container, see the Android Documentation at http://developer.android.com/reference/android/widget/RelativeLayout.LayoutParams.ht ml. As relative layout designs become more complex you will probably find that laying out interfaces in graphical view alone will not work. Specifically, controls will tend to try to establish a layout connection to other controls even when you want that connection to be to the parent view. It therefore becomes important to be able to specify layout parameters 78 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

directly in the xml file. Make sure you take the time now to become familiar with the various layout attributes, especially as they apply to relative layouts! A glance at the Layout folder in the palette in graphical view reveals many other forms of layout, but don’t panic... all of the other layouts in the palette are based on or derive from either a linear or relative layout. Knowing how these two types of layout work, you will easily be able to use any other form of layout.

3.3 Views and Controls While linear and relative layouts are interesting to us as developers, it is only when we populate them with controls that they become interesting or useful to the user. To understand controls, we must first understand views: everything the user sees and interacts with in an Android app is either a view or a subclass of the View class. To see the relationship between the View class and its subclasses, let's import the View class in MainActivity.java: import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View;

Now navigate to the View class under import declarations in the Outline panel:

and right click the android.view.View object (or just hit F4) to open the Type Hierarchy panel on the left: 79 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

We can see that View is a subclass of Object (which is the ultimate root class), and has many subclasses. Some of these subclasses are exposed for us in the Palette in the layout xml graphical view. Expand the ViewGroup class:

Here's our old friends LinearLayout and AbsoluteLayout, as well as other container classes that can hold groups of views. Expanding the LinearLayout class shows that table layouts and table rows are a type of linear layout, as are radio button groups and number picker controls. When in doubt as to the class hierarchy for a particular object, always turn to the Type Hierarchy tab! Notice also that as we navigate through the type hierarchy, the contents of the JavaDoc tab at the bottom of the Eclipse interface are also updated: 80 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Here we can read detailed information about a particular class as defined in the class itself. Also notice that below the Type Hierarchy display is a detail panel for the selected class:

Here we can see all of a classes' declarations, properties and methods, including overridden methods of a superclass it might contain (indicated by an upward pointing triangle). This panel is filterable in a variety of ways, and the Outline View on the right of the Eclipse 81 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

interface also reflects the content of this detail view. As there are too many types of control to list all the properties and methods here, make sure you take the time to familiarize yourself with the features of Eclipse that allow you to view class hierarchies and the properties and methods of classes. Just remember that all controls are subclasses of View: when you import android.view.View, all of the subclasses of View are available in the Hierarchical display.

3.3.1 Active and Passive controls Controls can be classified (somewhat artificially) into two main types: active and passive. An active control is one that the user expects some sort of interaction with, such as a button, an edit control, or a check box. A passive control displays some information to the user, but is not typically interactive. Examples of passive controls are text views, progress views, and layouts. Very generally speaking, if a control is used to say something, it is passive; if it is used to do something, it is active. Sometimes a control performs both actively and passively, as in the case of a button that's text changes when it is clicked. In such cases, a control is used both to say and do something: it is therefore acting actively and passively. Another case would be that of a clickable text view. In this case, since the text view has and onClick handler attached, it would be able to function as an active control, even if the user is unaware that it could do so. For this reason, we typically don't associate event handlers with text views unless some provision is made to inform the user that the control will do something when it is interacted with.

82 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

3.4 An Example: Processing text input In this section, we're going to develop a simple app that takes text input from the user and counts the number of times each word in that input appears. We don't know anything about getting text input from the user... but by using the tools available we should be able to write an implementation of this app. Let's start by finding a control that takes text input. In the Type Hierarchy of the View class, navigate among the various classes until you find a likely suspect. I'll wait right here... Here's a control that looks as if it would accept text input:

Let's see if we can make use of the EditText control in our application.

Start a new Android application named WordCount. Once the SDK is done creating the files, 83 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

open activity_main.xml in graphical view and delete the “Hello World” text view. Drag a new Multiline Text component from the Text Fields group in the palette to the layout, and resize and place it as shown:

Review the xml for the new control:

Note that this is an EditText object (not some mythical MultiLineEditText object) with the inputType attribute set to “textMultiLine.” What makes the EditText object accept multiple lines of input is this attribute alone. So we'll be looking at the methods and properties of EditText as we develop the app. If we run the app now, we can actually do quite a bit. With just an EditText positioned in the layout, we can enter multiple lines of text and view them:

84 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The keyboard can be dismissed by pressing the “back” button in the emulator. Obviously, this is just the beginning, but it's rather impressive that we can build even this much having not written a single line of code, don't you think? Let's think about what we want the app to do. We want to somehow count the number of times each word appears in the EditText control. For that, we will need to get the text string out of the EditText, then tokenize the string into words, probably using an array of strings. Then we will loop through the array, counting the number of times each unique word appears. For such a task, the HashMap class is perfectly suited: the keys will be the unique words as String, and the values will be the counts of the instance of each word, as an Integer. Let's add the array of string and the hash map to the Activity as properties: package com.ebook.wordcount; import java.util.HashMap; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View;

85 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public class MainActivity extends Activity { public HashMap wordCounts; public String[] words; @Override public void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } }

We'll need to have some sort of event to respond to in order to process the text input, so for now, we'll add a button to the interface:

86 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Notice that we've also created a new value in strings.xml for the button's text, and added an onClick attribute naming the method that will be called when the button is pressed. Save your work, and return to MainActivity.java. Now we'll add the processText method (The entire MainActivity.java file is shown): package com.ebook.wordcount;

import java.util.HashMap;

import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.Menu; import android.view.View;

87 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

import android.widget.EditText;

public class MainActivity extends Activity {

public HashMap wordCounts; public String[] words;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); wordCounts = new HashMap(); }

public void processText(View view) { //get the inputText control: EditText inputText = (EditText)findViewById(R.id.inputText); //get the text in the inputText, convert it to a string, //split it into words, and set the words array to the result: words = inputText.getText().toString().split(" "); for(String word : words) { if (wordCounts.keySet().contains(word)) { Integer count = wordCounts.get(word); count ++; wordCounts.put(word, count); } else { wordCounts.put(word, 1); } } //Log out the wordCounts HashMap for(String key : wordCounts.keySet()) { Log.i("processText:", String.format("Word: %s, Count: %d", key, wordCounts.get(key)));

88 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

} }

@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }

Since it is likely that the wordCounts hash map will be needed in more than one method of the class, we make it a property of the class itself, and instantiate it in onCreate. We could have made the words array local to processText, but we'll leave it as it is right now. processText first gets the text out of the EditText control by instantiating an EditText type called inputText, then getting its current text, converting it to a string, splitting that string on the “ “ character, and setting the words array to the result. Notice that we've renamed the id of the EditText control to “inputText.” We next enumerate through the words array. If the word is already in the set of keys of the hash map, we add one to the value for that key by getting the value, bumping it, and then setting the value. If the word isn't already in the hash map as a key, we add it with an initial value of 1. At this point, we want to test the algorithm to see if it works, so we just log out all the keys and values in the hash map. Here is a sample run of the app; the log statements appear in LogCat (from within the DDMS perspective in Eclipse):

89 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The words “one,” “two,” and “three” each appear once, “and” appears twice, and “a” appears three times. The log statements correctly reflect this, so our method appears to work! Writing out log statements is a useful way to test methods, but it's not very user friendly. We should figure out a nice way to display this information to the user. For now, let's use another EditText. Add a new Multiline Text control to the interface, and give it the id “outputText.” The graphical display should look similar to the following:

90 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

We've centered the new control vertically. Here is the xml definition for the control:

Save your work, and return to MainActivity.java. Add code to handle the display of text to the processText method: public void processText(View view) { //get the inputText control: EditText inputText = (EditText)findViewById(R.id.inputText);

91 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

EditText outputText = (EditText)findViewById(R.id.outputText); //get the text in the inputText, convert it to a string, //split it into words, and set the words array to the result: words = inputText.getText().toString().split(" "); for for(String word : words) { if (wordCounts.keySet().contains(word)) { Integer count = wordCounts.get(word); count ++; wordCounts.put(word, count); } else { wordCounts.put(word, 1); } } //Write out the wordCounts HashMap into outputText for for(String key : wordCounts.keySet()) { Integer value = wordCounts.get(key); String strKeyValue = String.format("%s : %d", key, value); outputText.setText(String.format("%s%s\n", outputText.getText(), strKeyValue)); } }

First we get an EditText named outputText by finding the control that has the outputText id. In the final for loop, we write out the key/value pairs in the hash by setting the outputText's text property. Here's a snapshot of the application running and displaying output:

92 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

At this point, many enhancements are possible (and desirable), but the basic functionality of the app is there. We could display the list in a second activity using a list view control, for example. Summary We've looked at layouts and controls in this chapter, and examined strategies for finding out about the various control's properties and methods. When confronted with an interface control that you know nothing about, you should take the following steps: � Drag the control to the interface. � Examine the xml to familiarize yourself with the control's attributes and tweak the positioning of the control within the layout. � Look at the view hierarchy for the control using the name of the control in the xml file, and examine its properties and methods. � If you still don't have enough information to begin coding, review the documentation for the control and its use on developer.android.com. � Use log statements while testing code to make sure everything is working as planned. The exercises that follow will test your ability to research and implement various controls.

93 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Exercises 1. Write an Android app that displays a RatingBar. Set the number of stars on the bar to 4, and set the rating to 2.5. Add two buttons below the rating bar: one to subtract 0.5 from the rating each time it is clicked, and one to add 0.5 each time it is clicked. Use a vertical linear layout. 2. Write an Android app that displays an EditText formatted as a password field. The user should be able to enter a password, then click a button. When the button is clicked, the password is compared to a hard-coded string within your app. If the password matches, change the background color of the layout to green, otherwise, change the background color to red. Use any layout container you like for this app. 3. Write an Android app that contains an ImageView and a button. Use the ic_launcher image for the image to be displayed in the ImageView. Each time the button is pressed, the image should rotate 45 degrees in the clockwise direction. For example, after the button is pressed 3 times, the image should look like this:

4. Write an Android app that uses a Spinner control and a TextView. The spinner should allow the user to select a digit from 0 to 9. When the digit is selected, display the english word for the digit in the text field. For example, if 8 is selected in the spinner, “eight” should be displayed in the text field. Use light yellow text over a black layout background.

94 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 4: ListViews and SQLlite

95 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

A typical android application programming task is to present a list of values to the user. When the user selects an item from the list, we might display detail about that item to the user in another activity, or start another process (a service for example). Using only the controls we've discussed so far, presenting data in a list format would be possible. For example, we might display several TextView controls in a vertical LinearLayout:

But this approach has severe drawbacks: it is not extensible (we can only add or delete items by adding or deleting TextViews in the layout), and there is no easy way to source the values of the text views from a data source other than hard-coding values within the activity itself. It turns out there there is a much easier way to present (and provide data to) a list of items: the ListView class. The class hierarchy of the ListView class is shown here:

96 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

As we can see, ListView is a subclass of View, but a direct subclass of a class called AbsListView. AbsListView is an abstract class: it is never meant to be instantiated, it just provides properties and methods to its subclasses. The full hierarchical pedigree of ListView is: ListView → AbsListView → AdapterView → ViewGroup → View → Object We also notice that AdapterView is a generic, meaning that ListView is also a generic! We can provide a datasource to a list view as a generic object: an array list, has map, or other generic type. This makes displaying list data very simple indeed. To further simplify the display of list views, the Android SDK includes a subclass of Activity called ListActivity. While we can certainly create and populate list views without using the ListActivity class, the steps to do so are a bit more involved. In this chapter, we'll explore the ListView class within the context of both the ListActivity class and the default Activity class. In the second half of the chapter, we'll introduce SQLite: an implementation of SQL present on all Android devices, and we'll explore using a SQLite database as the datasource for a list view. We've got a lot of ground to cover, so let's get started!

97 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

4. 1 Using ListActivity Let's begin with a simple app that displays a list of countries. When the user taps on a country in the list, we'll start a new activity showing some details about the country: its size, population, and so forth. In this example, we'll hold the data in a hash map of arrays. Later, we'll move the data to a database and access it through a content provider. To set up an application with a ListActivity as its main activity, we create a new Android Application Project as before. In this project, we'll use the following settings:

Do not create a custom launcher icon in the next screen:

98 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Choose a Blank Activity in the next screen:

and use the default settings in the final screen:

99 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Click on Finish, and wait for the application to be fully created. Open the MainActivity.java file. We've created the application with a MainActivity that is a subclass of Activity, since there is no option in the wizard to create the app with a ListActivity. But we can change the superclass of the MainActivity class very simply. Change the name of the superclass to ListActivity, and use the tool tip to import the ListActivity class:

(Generally, when you see a warning icon, always click it to see if a class in that line needs to be imported. If so, just click the import line to import the class).

100 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

A ListActivity doesn't need a layout xml file for the layout of the entire activity, so we can delete the setContentView statement from the onCreate method. We can also delete the activity_main.xml file in the res/layout folder. We do need to provide a layout xml file for the ListActivity, but only to define the layout of the items in the list. We'll do this next. Right click the res/layout folder, and create a new Android XML File:

Name the layout “list_element” and use a RelativeLayout as the Root Element. Add a TextView to the layout in the xml view of this layout file as shown:

Notice

that

we've

altered

the

layout_height

attribute

of

the

RelativeLayout

to

101 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

“wrap_content.” This is done so that the text view (which is currently the only control in the layout) will be tightly wrapped by the list items container. We've given the text view an id of “countryName” and set some attributes for the text so that it will be slightly larger than normal and padded by 10 device independent pixels on all sides. The next step will be to create an array of country names to display in each text view. Create a new Android XML File in the res/values folder, and name it “item_data.” Enter the following into the xml view of this file:

Great Britain France Spain Germany Switzerland India China Japan United States Brazil Argentina Chile Portugal Kenya Mali

Save your work! Now we're ready to set up the ListAdapter to import this information and display it in the ListView. In MainActivity.java, add these lines to the onCreate method as shown (the line beginning with this.setListAdapter... can be on a single line; I've broken it up to clarify the parameters):

102 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); String[]countries=getResources().getStringArray(R.array.countries); new ArrayAdapter( this this.setListAdapter(new this this, R.layout.list_element, R.id.countryName, countries)); }

Here's what's going on: we create a String array called countries into which we get the contents of the we declared in the res/values folder. That's the easy part. In the next line, we create a new ArrayAdapter to hold the elements that will be displayed in the ListView. Remember what this is: it is the instance of the ListActivity class that is currently running. ListActivity declares a method called setListAdapter. We use this method to instantiate an ArrayAdapter. The constructor requires four parameters: the ListActivity, the layout file for each item in the list, the id of the element to populate with data, and the array to use as a data source. And that's it! If we run the app right now, we'll see the list displayed with the contents of the countries array:

The list is scrollable, and if you tap an item in the list it briefly highlights. But other than that, 103 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

it isn't really very exciting, is it? It would be better if we actually did something when the user tapped an item. For that, we're going to need to define an event listener. An event is anything that happens on the device or in an app that may or should be responded to. Activity state changes are events, for example, and we have methods to listen for those state changes (such as onCreate, onResume, onPause, etc.). Other events are happening all the time: the phone rings, the user taps a button or other control, or the home or back key is pressed. In the case of a user generated event (such as a tap on a single item of a list view), we have the choice to listen and respond to the event or not. If we want the app to do something when the user taps a button, we must write an onClick handler. In the case of onClick, we can specify amethod name to execute in the layout xml using the android:onClick attribute. But in the case of any other event, we must set a listener up inside some lifecycle method, usually onCreate. So what is the event we should be listening to? Think about it this way... we need to be able to listen for a tap on a particular item in the list. Should we specify an onClick attribute in the list_element.xml? If so, how would we distinguish which element had been clicked? The answer lies in a superclass of the ListView, AdapterView, which defines a method called setOnItemClickListener. An OnItemClickListener listens for a click on a specific item in a list, and provides us with the list object that was clicked, the view inside the list on which the click happened, the position of the view as an integer, and the id of the view. The ListActivity class provides a simple way to find the list so that we can add the OnItemClickListener: protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); String[] countries = getResources().getStringArray(R.array.countries); new ArrayAdapter( this this.setListAdapter(new this this, R.layout.list_element, R.id.countryName, countries)); ListView listview = getListView(); new OnItemClickListener() { listview.setOnItemClickListener(new }); }

If you add the above code to your onCreate method, you will notice a warning message that 104 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

says that OnItemClickListener cannot be resolved to a type. Import the OnItemClickListener from the tooltip. Now the warning will state that there is an unimplemented method. Again, allow the system to create the method by clicking in the tooltip. The result is shown here: new OnItemClickListener() { listview.setOnItemClickListener(new @Override public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub } });

The parameters of the provided onItemClick method are not very descriptive; let's give them better names: new OnItemClickListener() { listview.setOnItemClickListener(new @Override public void onItemClick( AdapterView parent, View clickView, int position, long id) { // TODO Auto-generated method stub } });

(I've added whitespace above to show the four parameters more clearly). These four parameters are provided for us when the list view item is clicked. All we need to do is write code to handle the click. In this case, we want to add code to bring up a new activity. In this first implementation, we'll simply set a text view in the new activity to report the name of the country that was clicked. First, we'll need to add a new activity class to the application. Name it DetailActivity, and also add a layout xml file to res/layout using a Linear Layout. Name the file detail_layout.xml. Here is detail_layout.xml:

105 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



And here is the DetailActivity.java file: package com.ebook.listviewdemo1; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; public class DetailActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); this this.setContentView(R.layout.detail_layout); TextView chosenCountry = (TextView) findViewById(R.id.chosenCountry); Intent intent = this this.getIntent(); String countryName = intent.getStringExtra("country"); chosenCountry.setText(String.format("%s was chosen", countryName)); } }

This activity expects a string extra in the intent used to run it, so let's return to MainActivity.java and set up the intent: new OnItemClickListener() { listview.setOnItemClickListener(new @Override

public void onItemClick( AdapterView parent,

106 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

View clickView, int position, long id) { // TODO Auto-generated method stub RelativeLayout item = (RelativeLayout) clickView; TextView countryText = (TextView) item.getChildAt(0); String country = countryText.getText().toString(); this Intent detailIntent = new Intent(MainActivity.this this, class DetailActivity.class class); detailIntent.putExtra("country", country); startActivity(detailIntent); } });

Notice that we must cast the clickView to a RelativeLayout in order to get the TextView (and therefore the text) out of it. Since the TextView is the only item inside the layout, we can use getChildAt(0). The text is returned as an editable, so we must also convert it to a string. We put this string into an extra keyed as “country,” and fire off the detail activity. One thing remains before we can run the app: we must register DetailActivity in the manifest: android:label android:label=

107 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

activity > 0) { getContext().getContentResolver().notifyChange(uri, null null); } return count; }

The delete method takes three parameters: the Uri, WHERE clause, and WHERE arguments. Again, we match the form of the incoming Uri: if it is the table only form, we just call delete 169 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

against the database, passing the table name, where clause, and arguments. But if a specific row is called for in the Uri, we need to change the where clause to delete only that row. If a WHERE clause is provided as an argument to the content provider's delete method, we also append that existing information. This may result in now rows being deleted, of course, which is not an exception. The only exception that might occur results from an invalid form of Uri being passed in: we handle this in the default case by throwing an IllegalArgumentException. The database's delete method returns an integer indicating the number of rows that were deleted. If this integer is greater than zero, there has been a change in the data, and we must inform the content resolver of this change. Finally, we return the value of count. The insert and update methods are very similar: both are supplied with a ContentValues object in their parameter list. The function of an INSERT query is to add a new row to a database, UPDATE changes the content of one or more existing rows, if possible. The update method is slightly easier to implement, so let's tackle that one first: public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int count = 0; switch switch(URI_MATCHER.match(uri)) { case COUNTRY_LIST: count = database.update(CountryColumns.CONTENT_PATH, values, selection, selectionArgs); break break; case COUNTRY_ID: String strID = uri.getLastPathSegment(); String whereClause = CountryColumns._ID + " = " + strID; if (!selection.isEmpty()) { whereClause += " AND " + selection; } count = database.update(CountryColumns.CONTENT_PATH, values, whereClause, selectionArgs); break break; default default: throw new IllegalArgumentException("Unknown URI: " + uri); } if (count > 0) { getContext().getContentResolver().notifyChange(uri, null null); } return count; }

170 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The update method takes four arguments: the Uri, a ContentValues containing key/value pairs representing the column names (keys) and new values for the named columns (values), the WHERE clause, and WHERE arguments. Once again, we must distinguish between the two forms of Uri: if an ID is specified, we change the WHERE clause argument to reflect that before calling update on the database, and if an invalid Uri is passed into the function, we throw an exception. The database update method takes the table name and where information, and returns a count of updated rows. If this count is greater than zero, we inform the content resolver that there has been a change in the data. Finally, we return the count of items updated. Let's now look at insert: public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub if (URI_MATCHER.match(uri) != COUNTRY_LIST) { //we can't insert into a specific id: throw new IllegalArgumentException("URI not valid: " + uri); } //insert the values: long insertID = database.insert(CountryColumns.CONTENT_PATH, null null, values); //if the id is greater than 0, an insertion has been made: if (insertID > 0) { //tell the resolver(s): Uri itemUri = ContentUris.withAppendedId(uri, insertID); getContext().getContentResolver().notifyChange(itemUri, null null); return itemUri; } //if an insertion has not been made, throw an error: throw new SQLException("Error inserting into table: " + CountryColumns.CONTENT_PATH); }

A SQL INSERT always inserts a new row into a table, so passing the ID form of Uri makes no sense (for two reasons: ID's are automatically generated, and inserting a new row into an existing ID'd row is an invalid operation). If the Uri is not in the COUNTRY_LIST form therefore, we throw an exception. The database's insert method returns the id of the row just inserted; this is a long value. If no insertion was made, a zero will be returned, but this condition is an error, because an INSERT 171 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

statement should always succeed in adding a new row to a table. This is why we return from inside the if statement here: if the if testing clause is false, we throw an exception. Assuming we did insert a new row, we need to get the form of the Uri that specifies the ID of that new row. We do this by getting the ContentUri classes' Uri with the appended ID of the new row. We then notify the content resolver of the change, and return this Uri. We've left the implementation of getType for last, but it's really very simple: public String getType(Uri uri) { // TODO Auto-generated method stub String retVal = null null; //looking for either all rows or a specific row: switch (URI_MATCHER.match(uri)) { case COUNTRY_LIST: retVal = CountryColumns.CONTENT_TYPE; break break; case COUNTRY_ID: retVal = CountryColumns.CONTENT_ITEM_TYPE; break break; default default: throw new IllegalArgumentException("Unsupported URI: " + uri); } return retVal; }

All we're doing here is returning the “mime type” (from the CountryColumns) appropriate to the form of the Uri we passed in. In the case of a SQLite database store underlying the content provider, we can just return a string as discussed earlier (when we talked about the CountryColumns class). Let's take a minute to think about what we've done so far. We have a content provider which will connect to a SQLiteDatabase by using a SQLiteOpenHelper's getWritableDatabase method. This content provider will be accessed by activities in our application using a ContentResolver object. We've specified the operations needed to satisfy the CRUD requirements either in the SQLiteOpenHelper object (CountryDB.java) or here in the content provider. So really, all we've done is to create another layer of complexity between the underlying database and the activities that will access it. Here's the point: if all we're doing is accessing SQLite data within our own app, we should never use a Content Provider. BUT... if we intend to share the data with other applications, we must have a Content Provider. Remember that distinction: SQLite databases can exist very happily without a content provider within a single app. 172 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

I cannot stress enough that you study this implementation. If there is anything unclear about the structure of this content provider, re-read the above until you get the concepts clearly in mind. There really is no substitute for typing in the code yourself and studying it as you go! Don't be afraid to try things a different way... and learn from your attempts. Content Providers are a source of much confusion in Android development: understanding what you've done so far is a good first step toward understanding the (potentially) more confusing information presented later. Before proceeding to test the content provider, we'll add an entry to the manifest:

The two attributes to be defined here are the name (with the full package name) and the authority. The authority must match the AUTHORITY constant we set up in the content provider's constant properties. Now we can write a simple test activity to make sure the connection to the provider is working. This is the first pass at MainActivity.java: import java.util.ArrayList; import java.util.HashMap; import android.net.Uri; import android.os.Bundle; import android.app.Activity; import android.database.Cursor; import android.util.Log; import android.view.Menu; public class MainActivity extends Activity { HashMap countries = new HashMap(); @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this this.logCountries();

173 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

} private void logCountries() { Uri uri = CountriesProvider.CONTENT_URI; Cursor countryCursor = this this.getContentResolver().query(uri, null null, null null, null null, null null); if (countryCursor.getCount() > 0) { countryCursor.moveToFirst(); do { ArrayList details = new ArrayList(); details.add(countryCursor.getString(2)); //population details.add(countryCursor.getString(3)); //area // the key is the name of the country: countries.put(countryCursor.getString(1), details); details = null null; } while while(countryCursor.moveToNext()); } countryCursor.close(); //log out the hashmap: for for(String key : countries.keySet()) { ArrayList details = new ArrayList(); details = countries.get(key); Log.i("countries:", String.format("Country:%s, Pop:%s, Area:%s", key, details.get(0), details.get(1))); details = null null; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } }

We set up a hash map property to hold the contents of the cursor. In onCreate, we call a method (logCountries) to do the work. The first thing we do is establish the content Uri for the provider. This is given by the CountriesProvider's CONTENT_URI property. We then pass this to getContentResolver (along with nulls for the other arguments), which binds the content resolver to the content provider. 174 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Now we can use the methods of the content resolver to call the corresponding methods of the content provider! To get a cursor, we simply call the content resolver's query method, which makes a query call on the content provider. Supplying no projection, WHERE clause, arguments, or sort order is the same as setting up a query to return all rows. We move to the first row of the cursor, then loop through, getting the values in the cursor into our hash map (keys are the names of the countries, values are array lists containing the population and area). After all the rows of the cursor are copied into the hash map, we can close the cursor. Next, we log out the information in the hash map by looping through it. The output is shown here: countries:(1258): Country:United States, Pop:313,232,044, Area:3,718,691 countries:(1258): Country:Argentina, Pop:41,769,726, Area:1,068,296 countries:(1258): Country:Australia, Pop:21,766,711, Area:2,967,893 countries:(1258): Country:India, Pop:1,189,172,906, Area:1,269,338

We can provide this output in any format we wish, of course. Using the techniques learned in Chapter 4, we could display it easily in a ListView, for example. The focus in this chapter is learning how to work with content providers, so we'll stick with logging output (to keep the examples 'clean.') To get an idea of how to work with other query types, let's write an activity that inserts a new row into the database. First, we'll need a layout. Because insert and update are both forms of Update from the CRUD point of view (and can potentially use the same layout), I've named mine update_view.xml:

176 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



And the graphical view:

Here is the activity class (InsertActivity.java): package com.ebook.sqlite1; import android.app.Activity; import android.content.ContentValues; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; public class InsertActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.update_view); } public void insertClicked(View v) { EditText nameEdit = (EditText) findViewById(R.id.nameEdit);

177 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

EditText popEdit = (EditText) findViewById(R.id.popEdit); EditText areaEdit = (EditText) findViewById(R.id.areaEdit); if (nameEdit.getText().toString().isEmpty() || popEdit.getText().toString().isEmpty() || areaEdit.getText().toString().isEmpty() ) { return return; //do nothing... } ContentValues rowValues = new ContentValues(); rowValues.put(CountriesProvider.CountryColumns.NAME, nameEdit.getText().toString()); rowValues.put(CountriesProvider.CountryColumns.POPULATION, popEdit.getText().toString()); rowValues.put(CountriesProvider.CountryColumns.AREA, areaEdit.getText().toString()); Uri uri = CountriesProvider.CONTENT_URI; Uri rowURI = this this.getContentResolver().insert(uri, rowValues); Log.i("countries:", rowURI.toString()); this.finish(); } }

The if statement is in place to make sure that we don't try to add any NULL values to the row: we want to make sure to add a Name, Pop, and Area in each row, so if any of the edit texts are “empty,” we just return without doing anything. The content provider's insert method requires a Uri and a ContentValues. We set up a new ContentValues (rowValues), then put the column names as keys and the strings from the edit texts as values. Finally, we call the resolver's insert method (which calls the provider's insert method), and log out the result, then call finish() on the activity to remove the activity from view. In the MainActivity.java file, move the call to logCountries to the onResume method: protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }

protected void onResume() { super super.onResume();

178 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

this this.logCountries(); } //...

The reason to do this is that onResume will be called once the InsertActivity is no longer being displayed. The onResume method is also called when the MainActivity is first displayed, so moving this call still logs the data when the application is first run. Now we need to add an item to the MainActivity's menu. First, let's set up a menu.xml file in res/menu:

In the onCreateOptionsMenu method of MainActivity, point to this menu.xml file: public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu, menu); return true true; }

Then add a handler for the menu item, thus: public boolean onOptionsItemSelected(MenuItem item) { boolean retVal = true true; switch switch(item.getItemId()) { case R.id.menu_itemInsert: this Intent insertIntent = new Intent(MainActivity.this this, class InsertActivity.class class); startActivity(insertIntent); break break; default default: retVal = super super.onOptionsItemSelected(item); break break; } return retVal; }

If you were successful with the exercises in Chapter 4, this process will be very familiar. If the menu item tapped is our Insert item, the InsertActivity will be started. Don't forget to add the 179 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

entry for InsertActivity to the manifest before running the app! Here's sample log output, before and after inserting a new country. The log from InsertActivity is in blue: countries:(1797): Country:United States, Pop:313,232,044, Area:3,718,691 countries:(1797): Country:Argentina, Pop:41,769,726, Area:1,068,296 countries:(1797): Country:Australia, Pop:21,766,711, Area:2,967,893 countries:(1797): Country:India, Pop:1,189,172,906, Area:1,269,338 countries:(1797): content://com.ebook.sqlite1.CountryData/countries/5 countries:(1797): Country:United States, Pop:313,232,044, Area:3,718,691 countries:(1797): Country:NewCountry, Pop:987346, Area:6472876 countries:(1797): Country:Australia, Pop:21,766,711, Area:2,967,893 countries:(1797): Country:Argentina, Pop:41,769,726, Area:1,068,296 countries:(1797): Country:India, Pop:1,189,172,906, Area:1,269,338

With the information presented in this chapter, you should be able to write your own activities to delete and update rows in the database. These steps are left to you in the exercises.

180 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Exercises 1. Add an UpdateActivity to the application. The UpdateActivity should use the same update_view.xml as the InsertActivity, but should start with the three EditTexts filled in with strings from the row to be updated. In the MainActivity, present the result of the query call in a ListView. When the user taps an item in the list, marshal the data into an intent (as extras), the start the UpdateActivity with this intent. In the onCreate method of the UpdateActivity, disable the edit text showing the Name of the country to keep the user from changing this data. 2. Add a DeleteActivity to the application. Put a new menu item in the MainActivity's menu. When the DeleteActivity is displayed, present a list view containing only the country names from the database. When the user taps a list item, the item's row is deleted from the database, and the DeleteActivity is removed from view. For now, don't worry about “accidental deletions,” we'll cover methods for asking the user if they really want to delete an item later. 3. Add a SearchActivity to the application. In this activity, allow the user to enter a number for population or area (but not both), then click a button. Display only the names of those countries whose population or area (whichever was selected by the user) is greater than or equal to the number entered. 4. Write a new application that sources the content provider from this application. You will need to research how to access a foreign content provider: a good place to start is the content provider documentation on developer.android.com. Your new application should present a list view containing the data from the content provider only: there is no need to implement any other methods of the content provider than query.

181 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 7: App Organization

182 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Android applications are made up of components: Activities, Services, and Content Providers. But the notion of an “Application” tends to break down in Android development. We've seen that Services and Content Providers (and even Activities themselves) can be run from other applications through the use of Intent Filters and Content Resolvers. Most non – trivial “applications” actually use components from more than one app. Often, what the user perceives as a single application is really process flow from one component to another across multiple actual applications. The user experience across these components is often referred to as a task by Android developers. Even in a single application, there are usually multiple process flows (tasks). Consider an app that stores a list of notes in a database. In a single application model, the data would more than likely be accessed directly with SQLite (using SQLiteOpenHelper and SQLiteDatabase, but not a ContentProvider). There would be activities to list the data records, add a new row, modify a row, and delete a row. There would also likely be a Service or two, perhaps to email a note or to synch up notes with an server online. One user task would be adding a note to the database. This task involves two activities: the main display of notes and the activity that performs the insert. The second activity would not be involved in deleting a note or sending a chosen note via email: other activities or services perform these processes. The task flow in an Android app can rapidly become complex. With multiple task flows available to the user (possibly over many application's components), some way of organizing applications is essential. Android developers (and the Android platform itself) rely on proven design patterns to organize components to take advantage of the task flow model.

7.1 Use Cases Because the user experience in android is so task-centric, it helps to begin any project by thinking about the various tasks, or use cases, that a user will perform in the course of running the application: � What tasks should the user be able to accomplish? � What are the steps for the user to perform these tasks? ◦ When thinking about user tasks, think of as many as possible. Even if some will be dropped during the actual development process, it is better to specify more than less during the initial planning of a project. � What is the minimal set of user interfaces (activities) that will allow the user to perform these tasks? � What other components will be necessary (services, content providers, other classes)?

183 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

� Don't write a single line of code until the above questions (at least) are answered. Answering these questions gives us a rough estimate of what the application will “look like.” While they may seem very simple and “common sense,” the process of finding all of a project's use-cases is extremely important: it will give us an informed starting point for application design. As an example, let's look at one particular use case in our hypothetical note taking project: entering a new note. We assume that the main activity in the application presents a list of notes the user has already entered. Use Case: The user can enter a new note. o From the main activity, the user selects New Note... from the menu. o The Enter New Note activity is displayed o The user enters a title and text for the note. o The user clicks a button � The note is inserted into the database, with a time stamp corresponding to the time the button was clicked o The Enter New Note activity is finished, and the main activity is displayed. o The main activity re-queries the database and displays all notes. This use case would be part of the initial set of use cases for the notes project. Now let's assume that at some later point, you decide that the user should be able to set an alarm (or notification) of some sort on a note to be alerted about it at some point in the future. We might write another use case for this: Use Case: The user can set an alarm on a note. o From the main activity, the user taps a note. o The Set Alarm activity is displayed o The user can enter a date and time to trigger the alarm o The user presses a button to set the alarm � The alarm time is sent to a system service that handles alarms o The Set Alarm activity is finished, and the main activity is again displayed. This allows the user to set an alarm on a note that was previously entered. But this should also trigger a change to the first use case: 184 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Use Case: The user can enter a new note. o From the main activity, the user selects New Note... from the menu. o The Enter New Note activity is displayed o The user enters a title and text for the note. o The user can optionally enter a data and time for an alarm to be set. o The user clicks a button � The note is inserted into the database, with a time stamp corresponding to the time the button was clicked � If an alarm time was set, it is sent to a system service that handles alarms o The Enter New Note activity is finished, and the main activity is displayed. o The main activity re-queries the database and displays all notes. The alarm use case will also trigger changes in other use cases as well. For example, in a use case describing the user's ability to delete a note, the system service that handles alarms must be informed that the alarm time for the deleted note (if any) is no longer valid. We can see that having a fully considered set of use cases is crucial before beginning actual coding on any project. But even the most exhaustive set of use cases is insufficient by itself. Next, we must consider how the project can be best put together to handle the use cases we've defined. One of the most effective ways to begin this process is by considering how the project will fit into an overall Model – View – Controller design pattern.

7.2 Model – View – Controller Model – View – Controller, often referred to as a design pattern, is really a way of thinking about the overall structure of a project or application. The most basic premise of Model – View – Controller (or MVC) is that the classes and components that make up a piece of software should fall into three mutually exclusive groups: •

Those classes that deal with what the application is: its data and the operations on the data. These classes (taken together) are termed the Model.



Those classes that deal with how the application looks: its user interfaces. These classes are termed Views.



Those classes that control the function of the application and handle all communication between the Models and the Views. These classes are termed Controllers.

185 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The separation of these class types is more than just cosmetic. In particular, the communication between classes is strictly controlled in MVC: the Model and the Views never communicate with one another in any way. All communication between application components in MVC is handled by one or more controllers. This might seem to be an arbitrary restriction at this point, but later you will see that this communication restriction is vital to good software design using MVC. In Android development, we can consider the Views to be the layout XML files that present the user interfaces, and the Controllers to be the activities that drive these views. Activities also source data either directly, or indirectly through other classes. Any classes that deal with data or data operations are collectively considered to be the Model. In our notes app, we could outline a simple MVC diagram for the main activity, its view and the model:

Controller (Activity)

Model

View

(SQLite DB)

(layout XML)

The arrows in the diagram show that the controller sends instructions to the model (to execute queries) and to the view (to display information). But what is not shown is the communication from the model to the controller (to return the results of queries) and from the view to the controller (to get information entered by the user). As we've seen, when working with SQLite data, the controller obtains a cursor as the result of executing a query 186 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

against the database. This cursor is the return value of the query method itself, so the model – to – controller communication is implicit as a result of the controller – to – model communication. But what about the view? Communication from the view to the controller is always triggered by an event. In the event paradigm, the user initiates an action (by clicking a button, tapping an item in a list, or performing some other action) for which there is an event handler in the controller. This design pattern is referred to as Target – Action: the the target is the class that handles the event, and the action is the method that class uses to respond to that event.

7.3 Target - Action Target – Action is easiest to see in the case of a Button control handled by a method of an activity. In the example (TargetAction in this chapter's folder), we have named the layout XML file “view.xml” and the Activity “Controller.java” for clarity. In view.xml, we have a Button and a TextView:

187 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



The onClick handler method for the button is named action, again, to clarify the role of each element in the Target – Action pattern. In Controller.java, an action method has been declared: public void action(View v) { /* * The target is "this," the action is this.action * (this method) */ TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Hello, World!"); }

In the onCreate method of the Controller, the view for this activity has been set to view.xml: protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.view); }

The setContentView method specifies the view (the layout XML file) that this activity will handle events for. In other words, by specifying view.xml as the content view of the activity, the activity makes itself the target for UI events the occur on the view. The view.xml file specifies that the “action” method (in its target) be executed in response to a touch event on its button, by setting the android:onClick attribute of the button to “action.” So the target is Controller.java (because Controller.java's content view is view.xml) and the action is the action method (because that is the onClick attribute of the button). If we are declaring button handlers as methods of a controller, we can respond to more than one button's onClick event using a single handler. For example, if we were to add another button to the view (button2), we could rewrite our action method to switch among the buttons' ids and perform the correct action: public void action(View v) { /* * The target is "this," the action is this.action * (this method) */ TextView tv = (TextView) findViewById(R.id.textView1); switch switch(v.getId()) { case (R.id.button1) : tv.setText("Hello, World!");

188 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

break break; case (R.id.button2) : tv.setText("Bye!"); break break; default : break break; } }

When we are dealing with onClick events of an essentially similar nature, we should adopt this practice. Sometimes we declare an onClick listener as an instance of OnClickListener, and override the abstract method directly, as in this example: @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.view); Button btnBye = (Button) findViewById(R.id.button2); new OnClickListener() { btnBye.setOnClickListener(new @Override public void onClick(View v) { // TODO Auto-generated method stub TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Bye!"); } }); }

What is important to realize is that this is still Target – Action. Here's the full listing of Controller.java, so you can compare both action methods: package com.ebook.targetaction; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView;

189 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public class Controller extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.view); Button btnBye = (Button) findViewById(R.id.button2); new OnClickListener() { btnBye.setOnClickListener(new @Override public void onClick(View v) { // TODO Auto-generated method stub TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Bye!"); } }); } public void action (View v) { TextView tv = (TextView) findViewById(R.id.textView1); tv.setText("Hello, World!"); } }

The advantage of using methods of the controller to define actions is that it allows a single action method to respond to clicks on multiple UI elements in different ways. Any UI element may have an onClick attribute set to a method of its activity, as long as it is “clickable.” For elements that are not normally clickable, we can set android:clickable=”true” and then set the android:onClick attribute to the name of the appropriate click listener. One advantage of using the OnClickListener class anonymously (as in the case of button2's listener, declared in onCreate), is that there is slightly less overhead. Another advantage is that we are able to change the behavior of a clickable item at runtime by changing its OnClickListener or even disabling it. But since the OnClickListener is dedicated to a specific UI element, it cannot listen for clicks from multiple buttons. If we're handling onClick events, we can use either approach. But all other UI events must be handled by declaring an appropriate listener for the event. As said before, any View can be declared to have a listener; here are the event listeners for View objects: •

View.OnClickListener – a short touch event on a view

190 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



View.OnLongClickListener – a long touch event on a view



View.OnFocusChangeListener – when a particular view gets or loses focus



View.OnKeyListener – a hardware key event when the view has focus



View.OnTouchListener – when the user performs any touch event



View.OnCreateContextMenuListener – when a context menu is being built as a result of a sustained long click.

We will discuss all of many of these listeners in the next chapter. The important thing to keep in mind for now is that all UI event handling falls into the category of the Target – Action design pattern. So Target – Action is simple: in fact it's is a constraint of the Android platform. If we use layout XML files and drive them from activities, there is literally no other way to respond to user events than to use Target – Action. But what about the case in which something happens in a class that should be responded to by another class? As an example, suppose our note application wishes to inform the user if they attempt to insert a duplicate row into the database. One approach would be to have the model (the class that handles the data) try to execute a method of the controller directly by instantiating the controller and calling the method. But wait... the controller instantiates (and communicates directly with) the model, not the other way around. This approach would not work. What is needed is a way for the model to “tell the world” that something has happened. For this type of communication, we need a new design pattern: delegation.

7.4 Delegation In its simplest form (in Android) delegation occurs when we use startActivityForResult with an intent and get the result back from the started activity using onActivityResult in the first activity. This form of delegation occurs between controllers (the activities involved). The key point here is that the second activity really has no knowledge of what the first activity is, nor does it care. It simply returns its result. The first activity can do whatever it wants (or nothing at all) with that result. The second activity delegates its result and what to do with it to any activity that may have started it. Of course, there is no startModelForResult method, so this form of delegation will not work to communicate a runtime event from the model to the controller. For this, we will need to 191 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

implement an interface. The interface declares methods that will be performed by the delegate class (the controller) when they are called by the delegating class (the model). For example, we might write an interface containing a method duplicateRowEntered: public interface DatabaseEvents { public abstract void duplicateRowEntered(); }

Then implement the interface in our main activity (the controller): public class Controller extends Activity implements DatabaseEvents { //... }

When we declare that a class implements an interface, we are saying that the class will implement the abstract methods of that interface. In other words, by declaring that Controller implements DatabaseEvents, we are promising to implement the duplicateRowEntered method. Here is a simple implementation in which we log out the fact that the method was executed: public void duplicateRowEntered() { Log.i("delegate", "DUP!"); }

In the Model class, we call the method on the controller: public void delegate() { Controller c = new Controller(); c.duplicateRowEntered(); c = null null; }

Note that we must instantiate the Controller in order to get to the method, but we immediately set the Controller object to null afterward. In a realistic example, we would attempt to make the insertion only if the row were unique. If it were not, we would fire off the delegating class' implementation of the duplicateRowEntered method. In delegation using interfaces in Java, we must know to what class (or classes) we are delegating to. This is usually not a problem within our own apps since we will nearly always know the identity of the class(es) that should implement the interface methods. In cases 192 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

where this information cannot be known until runtime, we have other techniques to rely on, such as broadcasting an intent or simply returning an error code from a method (as in the case of our duplicate insertion check). Delegation using interfaces is a tool that we can use in cases where we know the identity of the class that should perform the delegated method. In some other languages, we can delegate “to the world.” In these languages, the identity of the delegate class is never known until runtime. Objective – C is an example of such a language. A simple example of delegation (in which the controller calls a method of the delegate class which calls a delegate back on the controller object) is included in the TargetAction project for your perusal. Summary of Communication in MVC The cardinal rule of MVC is to limit communication between the classes and components that make up an application. Controllers instantiate the Models and send messages (by calling methods) to them. In Android, Controllers are bound to a specific view by using the setContentView method. The messages that a controller sends to its view may alter the contents of the view or any of its subviews in some way. Messages from the View to the Controller are carried using Target – Action. The Controller is the target, and the action is the method (or event listener) that responds to an event on the view or on one of its controls. Delegation through intents (using startActivityForResult and onActivityResult) may pass information from one view / controller pair back to the controller that started that activity. Communication from the Model to the Controller can be conducted in basically three ways (with variations): •

Using delegation through an interfaces (in cases where the controller's identity is known at compile time)



Via the return values of called methods



By broadcasting an intent for which the controller implements a broadcast receiver.

7.5 Clear Separation of Classes When starting out using MVC, there is a tendency to muddy the distinction between the Model and the Controller. For example, when working with a SQLite database, there is a strong tendency to instantiate the database object directly within the Activity, and make query calls. We've done this in this book in the chapter on SQLite in order to present a high 193 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

level view of the techniques. As applications grow in complexity, it becomes increasingly important to clearly separate the Controller from the Model. The controller's only job should be to pass messages between the Model and the View: let the Model encapsulate all of the actual data (and operations on that data). Instead of instantiating a database within the controller, write another class that instantiates the database and deals with the underlying data store. Pass messages to this class from the controller. There are many advantages to this approach; two very important examples are: •

Encapsulation: by having the Model class or classes “self contained,” it is easier to reuse the model in other code.



Keeps classes small: Small classes (each with a single job to do) are much easier to debug and maintain than larger “catch all” classes. This is especially true in a team environment, where development tasks are typically divided according to expertise.

Keep in mind that an application is likely to contain more than one controller. In fact there will be one controller for each activity in the application, at least. There may also be controllers that do not correspond directly to an activity, whose purpose is simply to marshall data from one or more activities and pass it to one or more models. The most important rule to keep in mind is to confine your classes to a single process: a class that connects to a database and performs queries should only perform that specific function, if you find yourself writing methods to perform calculations using the data (for example), those methods should be factored out to another class. In the exercises that follow, you'll design the note taking application discussed earlier, starting with use cases, then expanding your ideas into a finished application. As you work through the exercises, think of ways to apply the steps taken in each exercise to other applications. There is no single right way to solve these five problems. The purpose of these exercises is to gain practice developing applications from start to finish.

194 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Exercises 1. Begin designing a note taking application by outlining use – cases. The application should allow the user to take a note, view all notes, change the content of a note, and delete a note. Outline the tasks that the user must be able to perform. More detail here is better than less: you can always remove, but it is harder to add. 2. Outline the classes necessary for the note taking application. Clearly separate these classes into Controllers and Models (Views in android are normally defined within XML files attached to controllers, right?) Make sure that your class graph(s) match the tasks to be performed as developed in your use – case document. No coding is to be done until exercises 1 and 2 are completed. 3. Begin writing the note taking application by defining the view XML files and the Activities (Controllers) that will drive them. Fully define the controller, but stub – out the calls to the model, using log statements or toasts. Test   your implementation. 4. Write the Model class(es). Implement the model data as a local SQLite database. Include fields for the _id, time note entered, note title, and note text. Make a clear distinction between the model and the controllers: do not instantiate the database within all the controllers for example, but extract the data connecting “stuff” to a class within the model. Since this database will be local, do not use a Content Provider. After finishing this exercise, write a test plan to follow. Cover all the use – cases within your test plan. 5. Finish   your implementation by making sure all “stubbed” functions written in exercise 3 are connected to methods within your model. Test the application by following the test plan developed in exercise 4. Write a bug report for any bugs found. Fix these bugs by returning to exercise 3, 2, or even 1 and following the steps outlined through this exercise. Are there any use – cases that feel “clunky” when using your application? If so, is this a result of how you defined the initial use – cases, or of how you implemented the class outline? How would you fix these types of problems?

195 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 8: Advance UI Components

196 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

At this point, with the knowledge you've gained from this book, you should be able to write some fairly advanced Android applications. In this chapter, we'll finish our discussion of UI controls by looking at some controls whose use is not immediately obvious. Most controls in the Android toolbox are very easy to use, and with the knowledge you've gained, you should have no problem figuring them out. For example, all the various layout types as well as ListView (as a subclass of AdapterView) are eventual subclasses of ViewGroup, and inherit its properties and methods. By looking at the type hierarchy and using the information you've already learned, the usage of most of the controls in the Android environment can be fairly easily gleaned. The goal of this chapter is not to examine every user interface control in detail, but to show a couple examples of controls that don't behave as we might expect. Detailed information on all Android controls is available at developer.android.com and via other internet resources. There are some controls however, that require a bit more explanation. We'll begin with one of the simplest of these controls: ToggleButton.

8.1 The ToggleButton control Let's begin by simply dragging a ToggleButton from the palette to the interface. The xml should look something like this (after removing the android:text attribute):

If we run the application now, surprisingly, the toggle button behaves as expected: 197 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

This is because a ToggleButton has a default behavior. By default, the texts of the two states of a toggle are “OFF” and “ON,” and the button starts out in the OFF state. Let's change the default off and on text properties. We first add a couple of strings to strings.xml: ControlDemo1 Hello world! Settings Red Blue

198 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

then refer to them in the layout file:

Now, when running the app, the texts for the button states have changed:

199 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

This automatic behavior is nice, but in order for the toggle to actually control something else, we must poll its state in an onClick handler. Let's add an android:onClick attribute to the button and a TextView to the layout:

(to simplify matters, we're just embedding the string “Color” in the android:text attribute for the TextView instead of making a string value). In the MainActivity.java file, we add a 200 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

property to the class for the TextView and bind its id in onCreate, setting its color attribute to Color.BLUE. In the btnClicked method, we adjust the color depending on the state of the toggle: public class MainActivity extends Activity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); tv.setTextColor(Color.BLUE); } public void btnClicked(View v) { ToggleButton btn = (ToggleButton)v; boolean isOn = btn.isChecked(); if (isOn) { tv.setTextColor(Color.RED); } else { tv.setTextColor(Color.BLUE); } } }

The counterintuitive thing about ToggleButton is that we don't have to change the state of the toggle in the click handler: this is done for us by the default behavior of the toggle button control:

201 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The reason the toggle button has this default behavior is that often we use the state of the button to direct the action of some other control. For example, another button control could behave differently depending on the setting of a toggle button. To see this, let's change the values of the toggle button strings to : ControlDemo1 Hello world! Settings Enable Disable

202 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

In the layout xml file, we'll add a button and make some adjustments as shown:

Note that we've moved the onClick attribute to the normal button from the toggle button, and done away with the android:text attribute in the TextView. Now we'll make some changes to 203 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

the MainActivity.java file: public class MainActivity extends Activity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); } public void btnClicked(View v) { ToggleButton toggle = (ToggleButton)findViewById(R.id.toggleButton1); boolean isOn = toggle.isChecked(); if (isOn) { tv.setText("Permission Granted"); //do something else: start an activity or service, etc. } else { tv.setText("Permission Denied"); } } }

Since the btnClicked method is now bound to the Button control (rather than the ToggleButton control), we need to first check the state of the toggle button by finding it by its id and inspecting the value of isChecked. Then we can set the text of the TextView accordingly. In a production application, we would certainly do something else of the toggle were on, such as setting up an intent or starting some other process. The important thing to recognize here is that the toggle button doesn't require a handler if we merely wish to inspect its value!

204 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

205 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

In each case, the action of the Go! Button is dictated by the state of the ToggleButton.

8.2 Radio Buttons Radio button controls are useful when we want the user to select a single item from a list of mutually exclusive items. But in the android layout manager, if we simply drag (for example) three radio buttons to the interface and run the application, we see immediately that they are in no way connected – in other words, selecting one of the buttons does not clear the other two. Let's first set up the app in this way. In the layout xml, we've added three radio buttons labeled Red, Green, and Blue:

Here is the running app, showing that all three buttons can be selected at the same time (which is intuitively wrong from the user's point of view):

207 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

In order to get the desired behavior, we must embed the three RadioButton controls in a RadioGroup control:

Running the app at this point allows only one button to be checked at a time:

208 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Once again, we see automatic behavior: the RadioGroup automatically deselects all buttons except for the one just selected by the user. (You may have noticed a warning message in the layout xml file to the effect that the RadioGroup layout or its RelativeLayout parent is useless. This is because both the RadioGroup and the RelativeLayout are subclasses of ViewGroup, and if a ViewGroup wraps only one “view” it's not “grouping” anything. We'll fix this problem in the next step by adding another view to the RelativeLayout.) In order to find which RadioButton in the group is selected, we can poll their checked values. Let's add a button and a text view to the layout:

209 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



Here is MainActivity.java: 210 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public class MainActivity extends Activity { RadioButton radioRed; RadioButton radioGreen; RadioButton radioBlue; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); radioRed = (RadioButton)findViewById(R.id.radioButton1); radioGreen = (RadioButton)findViewById(R.id.radioButton2); radioBlue = (RadioButton)findViewById(R.id.radioButton3); } public void btnClicked(View v) { TextView tv = (TextView) findViewById(R.id.textView1); if (radioRed.isChecked()) { tv.setText("Red Selected"); } else if (radioGreen.isChecked()) { tv.setText("Green Selected"); } else if (radioBlue.isChecked()) { tv.setText("Blue Selected"); } else { tv.setText("No Selection Made"); } } }

In onCreate, we find each RadioButton by its id attribute, then find which one is checked in btnClicked. By default, the starting state of buttons in a RadioGroup is “all unchecked” so we must include the final else clause here to trap if the user has not selected any RadioButton before tapping the Get Value button:

211 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

212 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Typically, some other control than a RadioButton is used to poll the state of the RadioButtons, as in the case of the normal Button control used here. But we can also set an android:onClick attribute for a RadioButton:

Here is the definition of the method in MainActivity.java: public void radioClicked(View v) { TextView tv = (TextView) findViewById(R.id.textView1); switch (v.getId()) { case R.id.radioButton1 : tv.setText("Red Radio Clicked!"); break break; case R.id.radioButton2 : tv.setText("Green Radio Clicked!"); break break; case R.id.radioButton3 : tv.setText("Blue Radio Clicked!"); break break; default :

213 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

break break; } }

When the user taps a RadioButton, this method will be called; the btnClicked method will still be called when the user taps the normal button. We can even re-write this method to be more terse: public void radioClicked(View v) { TextView tv = (TextView) findViewById(R.id.textView1); RadioButton rb = (RadioButton) v; String radioText = rb.getText().toString(); tv.setText(String.format("%s Radio Clicked!", radioText)); }

Here, we get the text directly out of the RadioButton and use it in a formatted string. Here's the running app immediately after clicking the Red RadioButton:

214 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

and after the Get Value button is clicked:

It is also possible to get the selected radio button directly from the RadioGroup, rather than polling each of the RadioButtons separately. In order to do this, we must set an android:id attribute on the RadioGroup itself:

In the MainActivity class, we must implement the OnCheckedChangeListener interface. We will need to set a property for the RadioGroup, and call setOnCheckedChangeListener(this) on the property in onCreate(). We can then define the action of the onCheckedChangeListener in a method, as shown: public class MainActivity extends Activity implements OnCheckedChangeListener {

215 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

RadioButton radioRed; RadioButton radioGreen; RadioButton radioBlue; RadioGroup radioGroup; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); radioRed = (RadioButton)findViewById(R.id.radioButton1); radioGreen = (RadioButton)findViewById(R.id.radioButton2); radioBlue = (RadioButton)findViewById(R.id.radioButton3); radioGroup = (RadioGroup)findViewById(R.id.radioGroup1); this radioGroup.setOnCheckedChangeListener(this this); } public void btnClicked(View v) { TextView tv = (TextView) findViewById(R.id.textView1); if (radioRed.isChecked()) { tv.setText("Red Selected"); } else if (radioGreen.isChecked()) { tv.setText("Green Selected"); } else if (radioBlue.isChecked()) { tv.setText("Blue Selected"); } else { tv.setText("No Selection Made"); } } public void radioClicked(View v) { TextView tv = (TextView) findViewById(R.id.textView1); RadioButton rb = (RadioButton) v; String radioText = rb.getText().toString(); tv.setText(String.format("%s Radio Clicked!", radioText)); } @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // TODO Auto-generated method stub String checkedText = null null;

216 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

switch (checkedId) { case R.id.radioButton1 : checkedText = "Red"; break break; case R.id.radioButton2 : checkedText = "Green"; break break; case R.id.radioButton3 : checkedText = "Blue"; break break; } this Toast.makeText(this this, String.format("%s Toast.LENGTH_LONG).show(); } }

checked",

checkedText),

The action here is to show a Toast. The application now shows three ways to access which radio button is checked: by polling its state with an external control, by polling each radio button's state as is is checked, and by polling the state of the entire radio button group. Which method is used will be chosen based on the needs of the application.

8.3 Fragments The Fragment class was introduced in API level 11 to group controls into logical units. This is especially useful on tablets, as the screen real estate supports a great many more controls than can be displayed at one time on a phone device. Each fragment has a similar lifecycle to the activity in which it is embedded: when the activity is paused (for example), all fragments within the activity are also paused. Fragments give us a way to treat a group of controls as a nearly autonomous unit within the activity in which they are embedded. In this demonstration, we will first set up a new emulator with a tablet form factor, then change the form factor in the layout xml to reflect the new emulated device. We will develop a simple application that uses two fragments. The goal in this demo is to show how to use fragments in an application, rather than to develop a truly useful app. The source code for the demo is in ControlDemo3. Let's first use the Android Virtual Device Manager to create a new AVD with the desired form factor.

217 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Click New... then make the selections as shown here:

218 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Click OK, and the new “Tablet” device should show up in the list as a valid android virtual device:

Exit the AVD Manager. Now create the new Android Application. When the template files are loaded, change the form factor of the layout xml as shown:

Run the app in it's current state to make sure the proper AVD is used. If it is not, then in the Project Properties, use the following settings:

219 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

When everything is set up properly, we should see the default application running in the Tablet AVD:

220 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Use the software “back” button at the bottom of the screen to quit the running application. Now we can begin coding the application. Each fragment in the application must have a class assigned. This class must be a subclass of Fragment (or one of its subclasses). As we shall see, a fragment is very similar to an activity, in that it has lifecycle methods of its own that we can override to get desired results. This image (from developer.android.com) shows the fragment lifecycle:

221 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

222 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The important methods to override are: � onCreate – to initialize components of the fragment that should be retained over pausing or stopping. � OnCreateView – must return a View that is the root of the fragment's layout, hence this method must always be overriden. � OnPause – useful for saving application state if necessary: the user may not return to this fragment or its enclosing activity. Let's start by adding a subclass of Fragment named Fragment One:

223 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Make sure that the FragmentOne class is a subclass of Fragment! The template code is not very helpful, is it? package com.ebook.controldemo3; import android.app.Fragment; public class FragmentOne extends Fragment { }

Let's begin by overriding the onCreateView method: public class FragmentOne extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_one, container, false false); } }

onCreateView takes three parameters: a LayoutInflater that will be used to set the Fragment's layout xml file, a ViewGroup that serves as the container for the fragment, and the Bundle. In this simple implementation, we return the result of inflating the layout, which is a View.

We have an error because we have not yet defined the fragment_one.xml file in the 224 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

res/layout folder. We should do that now:



For simplicity, we've hard coded the strings for the radio buttons and the text view, but in a production app, we would use @string resources for this purpose. Now that we've got our fragment layout, we can add it to the activity_main.xml file. From the palette, drag a Fragment control to the layout. From the popup list, choose FragmentOne:

226 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

After selecting FragmentOne, we see this prompt in the graphical layout screen:

Right click the fragment:

and select “Choose Layout...” as shown above. The following dialog will be shown:

227 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

choose fragment_one as shown above, then click OK. The layout of the fragment will be displayed inside the main layout:

We can now move this fragment within the layout to any location we wish, as if it were a 228 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

single control! Let's position it to the left of the layout, centered vertically:

If we run the application now, we can see the result of our work. It does nothing as yet, but the radio buttons are active, and because we've embedded them in a radio group, only one can be chosen at a time:

229 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

So far so good! Let's create a second fragment. What should be our first step? Well, if we create the class first, we'll get an error until we define the layout file for the fragment, as we did for FragmentOne. It's really no big deal: you can create the class first or the layout first. This time, we'll create the layout first:

230 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



Here, we merely have a RelativeLayout with a centered TextView containing the text “Color” in white at 72 pixels:

Now we can create the FragmentTwo class: public class FragmentTwo extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_two, container, false false); } }

Finally, we can add this fragment to the activity_main.xml file:

231 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



Note that the activity_main.xml file is very simple, it wraps the two fragments inside a RelativeLayout, and specifies some attributes for each. In particular, each fragment has an assigned id, and an android:name attribute that points to the fully qualified class name of the fragment. The layout file for each is specified in the tools:layout attribute, and the tools: namespace has been included for us at the top of the file (in the RelativeLayout container). The graphical view of activity_main.xml should look something like this:

232 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

We'll run the app again at this point to see the results of our work:

233 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Again, there's not much going on here, but we can see that both fragments are being displayed. Examination of the id inner class in R.java reveals that all of the components (along with the fragments that contain them) are available as id attributes at the top level:

234 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

So we should be able to write MainActivity.java in such a way as selecting a radio button in fragment one will change the color of the text view in fragment two. Here's the implementation: public class MainActivity extends Activity implements OnCheckedChangeListener { RadioGroup radioGroup; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); radioGroup = (RadioGroup)findViewById(R.id.radioGroup); this radioGroup.setOnCheckedChangeListener(this this); } @Override public void onCheckedChanged(RadioGroup rGroup, int selectedId) { // TODO Auto-generated method stub TextView tv = (TextView) findViewById(R.id.tvColor); switch (selectedId) { case R.id.radioButton1 : tv.setTextColor(Color.RED); break break; case R.id.radioButton2 : tv.setTextColor(Color.GREEN); break break; case R.id.radioButton3 : tv.setTextColor(Color.BLUE);

235 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

break break; } } }

And the app works as expected:

This is obviously a very simple example, and we could have developed it without the use of fragments. But it shows how to implement fragments in an android application. Fragments can be of arbitrary complexity; they are really mini-activities inside an activity. They can launch intents to start other activities or services, source data from a content provider, etc. In their simplest state, fragments can be used to group controls to create “new” composite controls, even on phone devices. Fragments can also be managed by the enclosing activity: the Android SDK supplies a fragment manager for this purpose. Using the fragment manager, fragments can be added or removed from the activity's display using a transaction – based api. The developer.android.com web site has more information on using the FragmentManager API, 236 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

which allows the creation of very sophisticated user interfaces. A full discussion of the API is beyond the scope of this book. Summary In this chapter, we've looked at ToggleButton and RadioButton: two commonly used controls whose use is not immediately intuitive. We use these controls as examples, but there are many controls whose use may be classified as “tricky.” We do not give examples for each and every control in the android toolbox (which would fill an entire book in and of itself, besides being redundant information already available via other sources, such as the developer.android.com site). We've also looked at Fragments: groups of controls which can be treated as single units. In addition to their use in tablet applications (where the screen size warrants such control grouping), we've hinted that fragments could be used as composite controls. This is the preferred way to create such controls at this point, rather than overriding the ViewGroup class or some Layout class and adding discrete controls to it.

237 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Exercises 1. Write an application that uses two toggle buttons to mimic the action of a logical OR gate. If either or both of the toggle buttons are ON (in the checked state) then a radio button in the interface is set to checked, otherwise, the radio button should be in the unchecked state. Do not allow the user to change the state of the radio button. 2. Repeat exercise 1, this time mimic the operation of a logical AND gate. If and only if both the toggle buttons are in the checked state, the radio button is set to checked. 3. Repeat exercise 1, this time mimicking the operation of a logical XOR gate. The radio button should be in the checked state if one (and only one) of the toggle buttons is in the checked state. 4. Write an app that displays two radio buttons in a radio group. Add two activity classes to the application, as well as an Intent property to the main activity class itself. When the user selects a radio button, set the Intent to either the first activity or the second, depending on the selection, but do not start the activity until a separate Button control is clicked by the user. 5. Write an application that uses two fragments. Each fragment should display two toggle buttons: the first fragment's toggles should function as an AND gate; the second fragment's should function as an XOR gate. Use the toggle buttons values to set a boolean property in each fragment. Set the fragments up such that the output of the first fragment controls the second fragment's first toggle button: 6. The three user-controllable toggle buttons are labeled a, b, and c in the diagram above. The button labeled a && b is not user-controllable, it is set by the output of fragment one. The circle on the right represents a radio button as in exercises 1, 2, and 3. Test the

a ◦ application against this set of inputs and outputs: AND

b

XOR

a && b

c

238 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

A

B

C

OUT

Off

Off

Off

Off

On

Off

Off

Off

Off

On

Off

Off

On

On

Off

On

Off

Off

On

On

On

Off

On

On

Off

On

On

On

On

On

On

Off

239 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Chapter 9 – Animation and Graphics

240 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

The Android API offers several powerful techniques to add animation and graphics to applications. In this chapter, we will take a look at these tools.

Animation is the act of changing an object's properties over a period of time. In Android, there are two techniques that can be used to do this: Property Animation and View Animation. Property Animation allows us to animate the properties of any object (not only those displayed on the screen). It was introduced in Android 3.0 (API level 11), and is today the preferred way to animate objects. View Animation allows us to change the properties of a view object over time. It is the older API, but since much of the android installed base is running 2.0 or 2.3, it is necessary that we understand both property and view based animation. We will begin our discussion of animation with view based techniques. The Android platform also offers two distinct ways to display graphics: a 2D approach in which we draw points, lines, and rectangles on a canvas for display, and 3D graphics using OpenGL – ES. We will take a brief look at both of these approaches.

9.1 View Based Animation View based animation has been available since the earliest versions of the API. It is both more limited and easier to implement than property based animation, so if what we need to do can be handled using view based animation we should do so. The animations to be applied to a view are specified in an XML file in the res/anim folder of the project. This file (like all xml files) must have a single root element: in an animation xml file, the root element must be , , , , or . The element is a container: it can hold one or more other elements (including other elements). All animations in the xml file will be applied simultaneously, unless an android:startOffset attribute is given for an animation. This attribute's value is specified in milliseconds. There is also an android:duration attribute for animations (again, specified in milliseconds) that controls the length of time an animation should take. Each animation in the xml file is applied to the entire view to which it refers, not to any specific property of a view. In order to animate properties, property based animation must be used. The code for the following example is in the Animation1 folder for this chapter. As always, we suggest you write the code from scratch, but if you have any trouble, refer to the supplied source code. Let's first supply a simple user interface for this app. Here is the activity_main.xml layout file: 241 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



The following additional string resources are used in the layout: ViewAnimation Hello world! Settings Animating! Start

With these settings, the graphical view should look something like this: 242 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

When the Start button is pressed, we will animate the text view to first move upward, then move back downward until it is in its original location. We will perform this animation each time the button is pressed. If you look in the resource folder, you will notice that there is no anim folder present. That's OK, we can simply make one. Right – click on the res folder, select New > Folder... and name it “anim”. Now right – click the newly created anim folder, select New > Android XML File... and apply these settings:

243 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

We use as the root element because we are applying two animations: a translate (up), and a second translate (back down). Since we intend to perform these animations in sequence, we will need to supply a duration for each, and a startOffset for the second. Here is the textview_animation.xml file:

244 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



The element wraps the two elements of the animation. The first animation uses an decelerate interpolator, meaning that the text view will appear to slow down during the course of the animation. We use a accelerate interpolator to make the text appear to speed up as it falls back to its original location. The duration for both animations is set to 6/10 of a second, the second animation will begin 6/10 of a second after the first (set by its startOffset attribute). The remaining attributes set the starting and ending (x, y) coordinates of the text view relative to its parent view (the RelativeLayout of activity_main.xml). The from?Delta attributes specify where to start the animation in relation to where the view currently is, the to?Delta attributes control where the view will end up. We're moving the view first 100 pixels up, then 100 pixels back down. The coordinate system for view animations has its origin in the upper left corner, with x increasing toward the right and y increasing downward. Moving upward is therefore moving in the negative y direction: the exact opposite of the standard cartesian coordinate system. We apply this animation to the TextView in the MainActivity.java file: public class MainActivity extends Activity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } public void animateText(View v) { this Animation moveText = AnimationUtils.loadAnimation(this this, R.anim.textview_animation); tv.startAnimation(moveText); }

245 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

}

We have made the TextView a property of the class so that we don't have to instantiate it every time the button is pressed. In onCreate, we've set the property by finding the view by its id. In the animateText method (the button onClick handler), we first make a new Animation object (moveText), getting the return value of the static loadAnimation method of the AnimationUtils class. We pass this method a context and the xml file that defines the animation. Then we apply the animation to the text view using the startAnimation method, passing the moveText animation. When we click the button, the text appears to move up then down over a period of 1.2 seconds. A video of this demo running is in this chapter's folder (ViewAnimation1.mov). Let's suppose we want to change the animation so that the text view appears to rotate 360 degrees when the button is pressed. Since the text view is centered both horizontally and vertically, we can rotate it about the center point of its containing view (the RelativeLayout). This should be simple enough... we can add the rotation element and comment out the translate elements thus: --> -->

We've used a linear interpolator in the element; this will maintain a steady speed throughout the animation. A video of the app running with this animation xml is included (ViewAnimation2.mov). If the Text View were not centered, making it appear to rotate about its own center would be more difficult, because rotation always occurs relative to the view containing the object we are rotating. One way to get around this is to place the text view in the center of another layout container (shown here in activity_main.xml:

247 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved



The inner RelativeLayout is sized big enough to hold the text view as it is rotating. This is important: if the layout is not big enough to hold the rotating text view, the rotation will be “clipped” by the layout. Also note that the button is now set to be 48 pixels below the new layout, not the text view (as before). See ViewAnimation3.mov for a run of the app using this animation. As a final step, we could try to move the text up, then rotate it, then move it back down. Our first attempt might look something like this:









Let's give the inner RelativeLayout a color to see its bounds in activity_main.xml:

When we run this version of the app, the text view moves, then rotates, then moves again, but the rotation (being about the center of the inner RelativeLayout) is perhaps not what we expected (ViewAnimation4.mov). All animations on a view are made relative to the view that contains them! So how can we change things so that the rotation appears to occur about the center of the text? One strategy would be to apply the translate animations to the inner RelativeLayout, and the rotation to the text. For this to work, we'll have to move the translate animations to a new animation xml file. Let's call it layout_animation.xml:

textview_animation.xml now looks like this:

Note that we've kept the durations and startOffset values the same: we'll be running two animations concurrently. The adjustments required to MainActivity.java are: public class MainActivity extends Activity { TextView tv; RelativeLayout innerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1);

250 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

innerLayout = (RelativeLayout) findViewById(R.id.textViewContainer); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } public void animateText(View v) { this Animation moveLayout = AnimationUtils.loadAnimation(this this, R.anim.layout_animation); this Animation rotateText = AnimationUtils.loadAnimation(this this, R.anim.textview_animation); innerLayout.startAnimation(moveLayout); tv.startAnimation(rotateText); } }

The animations for the innerLayout and the text view start virtually at the same time, but since the startOffset values are specified, the text view animation (the rotation) waits 600 milliseconds before beginning, which gives the innerLayout's translate animation time to run. Likewise, the second translate animation in the innerLayout waits 1200 milliseconds before starting so that the first translate and the rotation of the text view have time to finish. The result of this is shown in ViewAnimation5.mov. Using the techniques of view animation, we can get fairly good results if we plan our animations carefully. But view animation is limited: it only allows animation of the entire view, not of any properties within the view. This means that we can move, rotate and scale the view and change its alpha channel value, but not (for example) animate a change in color. Also, the underlying properties of an animated view do not change: as an example, try setting the android:toDegrees attribute to 180 in textview_animation.xml. Immediately after the text animation is done, the text “snaps back” to its original upright position (See ViewAnimation6.mov). This is because the TextView's underlying rotation property isn't changed by the view animation. View animation animates the entire view; it cannot animate properties. When View animation is over, the animated view appears with all its original properties intact! View animation is often sufficient to our needs, and should always be used when animation 251 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

is required unless we need to perform some action it cannot provide. In such cases, (and if we are writing for API level 11 or higher), we must turn to Property Animation techniques.

9.2 Property Animation Property animation can be applied to change the properties of any object (not just views) over time. In order for property animation to work, the object's class must define a setter of the form set. For example, if there is a property named foo that we wish to animate, a setter named setFoo must be defined. Note the the first letter of the property is capitalized in the setter. If we need to define a getter, it must be named getFoo (for the foo property).2 The base class used to perform property animation is ValueAnimator. This class allows animation through a set of int, float, or color values. This is done via the ValueAnimator factory methods ofInt(start, end), ofFloat(start, end), and ofObject(start, end). This animates values through a set period of time, but does not directly animate the properties of a class. When using ValueAnimator, we must also use callbacks during the animation to set values to the starting, intermediate, and ending values. Since this can be tricky (and also require significant coding), the Android SDK supplies us with a subclass of ValueAnimator called ObjectAnimator. ObjectAnimator wraps the value setting and timing capabilities of ValueAnimator and adds the ability to directly manipulate an object's properties over time, without resorting to callback methods. This makes property animation almost embarrassingly simple. We'll begin our study of property animation with a project named PropAnimation (in the Animation2 folder for this chapter). Once again, we advise you work through the example by entering the code as directed here in the text. To begin, we have a activity_main layout:

Once again, we have a button and a text view; the animation will begin when the button is pressed and the btnClicked method is fired. Two string resources have been defined for the button and text view: PropAnimation Hello world! Settings Animation! Start

Our implementation of MainActivity.java should look like this: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }

253 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public void btnClicked (View v) { TextView tv = (TextView) findViewById(R.id.textView1); ObjectAnimator textviewAnimation = ObjectAnimator.ofFloat(tv, "rotation", 0f, 180f); textviewAnimation.setDuration(1000); textviewAnimation.start(); } }

All the action occurs in btnClicked. First, we find the text view by id so we can manipulate it via property animation. The next line is a bit new: we make an ObjectAnimator object called textviewAnimation by calling the ofFloat factory method of ObjectAnimator, supplying these parameters: � the name of the object � the name of the property we want to animate as a string � The values between which to animate: if only 1 value is supplied, it is assumed to be the ending value, and a getter (get) must be present for the animated property. If there are two values supplied, they are: ◦ the starting value of the property ◦ the ending value of the property. In this very simple case, we want to rotate the text view 180 degrees about its own center. By default, the rotational center when using property animation is the center of the object we are animating, so that part is taken care of for us. If we look at the methods of the View class, there is a setRotation method which takes a single parameter of type float specifying the angle in degrees. The ofFloat factory method is used here because the parameter of the setRotation method must be a float. We then set a duration for the animation of one second, and call the start() method to begin the task. That's it! Watch the PropAnimation1.mov video to see this version of the app run. You will notice that the rotation property of the text view stays at 180 degrees until the button is clicked again to re-run the animation. This is because we are starting the animation with the rotation property set to 0 degrees each time: ObjectAnimator textviewAnimation = ObjectAnimator.ofFloat(tv,

"rotation", 0f, 180f);

254 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

It would be nice if the rotation started each time from the value it stopped at the time before. We can accomplish this easily by adding a float property to the MainActivity class: public class MainActivity extends Activity { float startAngle = 0; @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnClicked (View v) { TextView tv = (TextView) findViewById(R.id.textView1); ObjectAnimator textviewAnimation = ObjectAnimator.ofFloat(tv, "rotation", startAngle, startAngle + 45); textviewAnimation.setDuration(1000); textviewAnimation.start(); startAngle += 45; } }

Here, each time the button is pressed, the text view rotates through an angle of 45 degrees, each time starting where it ended the previous time. See PropAnimation2.mov for this version of the app. Even though XML files can be used with property animation (see developer.android.com for more information on this), we can also control sequential animations by using an AnimatorSet. Let's suppose we want to animate the text view so that it first goes up and left 100 pixels, then straight right 150 pixels, then returns to its original location. During the horizontal move, we'll also rotate the text 180 degrees. If we supply only one value to an ObjectAnimator, the assumption is that it is the final value for the animation. In this case, there must be a getter for the property we are animating. Here is the new MainActivity.java: public class MainActivity extends Activity { TextView tv = null null; float startAngle = 0; float originalX = 0; float originalY = 0;

255 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

@Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); } public void btnClicked (View v) { originalX = tv.getX(); originalY = tv.getY(); //first AnimatorSet: ObjectAnimator anim1moveX = ObjectAnimator.ofFloat(tv, "x", tv.getX() - 100); ObjectAnimator anim1moveY = ObjectAnimator.ofFloat(tv, "y", tv.getY() - 100); anim1moveX.setDuration(1000); anim1moveY.setDuration(1000); AnimatorSet set1 = new AnimatorSet(); set1.play(anim1moveX).with(anim1moveY); //second AnimatorSet: ObjectAnimator anim2moveX = ObjectAnimator.ofFloat(tv, "x", tv.getX() + 150); ObjectAnimator anim2rotate = ObjectAnimator.ofFloat(tv, "rotation", tv.getRotation() + 180); anim2moveX.setDuration(1000); anim2rotate.setDuration(1000); AnimatorSet set2 = new AnimatorSet(); set2.play(anim2moveX).with(anim2rotate); //third AnimatorSet: ObjectAnimator anim3moveX = ObjectAnimator.ofFloat(tv, "x", originalX); ObjectAnimator anim3moveY = ObjectAnimator.ofFloat(tv, "y", originalY); anim1moveX.setDuration(1000); anim1moveY.setDuration(1000); AnimatorSet set3 = new AnimatorSet();

256 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

set3.play(anim3moveX).with(anim3moveY); //Controlling AnimatorSet: AnimatorSet playAll = new AnimatorSet(); playAll.playSequentially(set1, set2, set3); playAll.start(); } }

Because we want to return to the original (x, y) location at the end of the animation, we preserve these values at the beginning in properties of the class. Set1 is created to contain the first move (both in the x and y direction); it is directed to play its two animations “with” one another, at the same time. Set2 also contains two animations, a move of the x coordinate to the right, and a rotation of 180 degrees, which will also be played concurrently. Set3 is essentially the same as set1, this time moving x and y back to their original locations. AnimatorSets can be nested; we do this with playAll, which simply plays set1, set2, and set3 sequentially. Finally, we start the animation. As you can see by viewing PropAnimation3.mov, the value of the rotation is maintained between calls to btnClicked: we could not do this with simple view animation (because the text view would revert to its normal appearance after the animation). As we can see, property animation is very powerful, but more code is required for all but the simplest forms of animation. View animation requires less code, but an animation xml file must be present to control the animation. If what we wish to accomplish only requires view animation, we should rely on view animation to get the job done.

9.3 2D Graphics There are several ways to draw graphics on the android screen ranging from simply drawing directly on a view to drawing “drawable” resources and applying transforms to them. Essentially though, they all boil down to the same thing: using graphics primitives to place or draw objects on some surface, whether it is a canvas in a dedicated view, or directly on the surface of an existing view. For drawing simple line graphics in Android, the approach usually taken is to subclass View, overriding some of its methods, then load the new view in the MainActivity's onCreate method. We'll take this approach in this section. (The project is included in this chapter's folder as Graphics2D.) We'll begin by subclassing View, adding a new class named GraphicView: 257 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

package com.ebook.graphics2d; import android.content.Context; import android.view.View; public class GraphicView extends View { public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub } }

The default View subclass has a single – argument constructor which takes a context as a parameter. There is obviously much work to be done here, but let's first add this view as the content view of MainActivity.java: public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super super.onCreate(savedInstanceState); this setContentView( new GraphicView (this this)); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true true; } }

We've replaced the layout xml with an instance of GraphicView, and passed the activity as the context. Now, when we run the application, the activity will show a GraphicView instance instead of the layout xml. In order to draw into a view, we must override two methods of the view: onMeasure and onDraw. The onMeasure method is responsible for setting the size of the view object. The onDraw method performs the actual drawing when the view must be redrawn. Let's add 258 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

these methods as stubs to GraphicView: public class GraphicView extends View { public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub } int widthSpec, int heightSpec) { protected void onMeasure(int super super.onMeasure(widthSpec, heightSpec); } protected void onDraw(Canvas canvas) { } }

onMeasure must call up to super, passing the width and height parameters. We will do more with these two methods shortly. The screen width and height will be important to us for drawing graphics, so we'll make properties of these and set them in the onMeasure method. We'll also need to set up a Canvas to hold the drawing, and a Bitmap object to actually draw on. We use underscores here to avoid conflicts later on: public class GraphicView extends View { int screenWidth; int screenHeight; Canvas _canvas; Bitmap _bitmap; public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub } int widthSpec, int heightSpec) { protected void onMeasure(int super super.onMeasure(widthSpec, heightSpec);

259 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

//get the screen width and height: screenWidth = View.MeasureSpec.getSize(widthSpec); screenHeight = View.MeasureSpec.getSize(heightSpec); //set the dimension of the view: setMeasuredDimension(screenWidth, screenHeight); //set up the Bitmap, and use it to create the Canvas: _bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); _canvas = new Canvas(_bitmap); } protected void onDraw(Canvas canvas) { } }

In onMeasure, we set the screenWidth and screenHeight to the size of the screen. Then we set the measured dimension of the GraphicView to these values. We create a Bitmap of this size, with a configuration of ARGB_8888. This configuration stores each pixel in 4 Bytes, and gives 8 bits of storage to each of the Alpha, Red, Green, and Blue channels, allowing 256 distinct values for each channel. This is standard ARGB encoding, and should be used whenever possible. Finally, we create a new Canvas object using this bitmap. Running the app now is really boring, but at least we can see that it's working:

260 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Ok, let's do some actual drawing now. To draw anything, we need a Paint object. Again, since we'll be painting each line with the same color, it makes sense for this to be a property of the class. We'll instantiate this property in the constructor: Paint paint; public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE);

261 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

}

The paint object has been given a red color and a stroke (solid line) style. In this demo, we'll draw a square centered about the center of the view. A square requires four lines. Each line is defined by two points, so we'll need four two – element arrays of type Point. These should also be properties: we'll set the values in one method, and perform the actual drawing in another. Here, we define the makeSquare method, which sets these values. (We assume that the line1, line2, line3, and line4 arrays are already properties): public class GraphicView extends View { int screenWidth; int screenHeight; Canvas _canvas; Bitmap _bitmap; Paint paint; Point line1[]; Point line2[]; Point line3[]; Point line4[]; public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); } protected void onMeasure(int int widthSpec, int heightSpec) { super super.onMeasure(widthSpec, heightSpec); //get the screen width and height: screenWidth = View.MeasureSpec.getSize(widthSpec); screenHeight = View.MeasureSpec.getSize(heightSpec); //set the dimension of the view: setMeasuredDimension(screenWidth, screenHeight); //set up the Bitmap, and use it to create the Canvas:

262 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

_bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); _canvas = new Canvas(_bitmap); } private void makeSquare() { int centerX = screenWidth / 2; int centerY = screenHeight / 2; line1 = new Point[2]; line1[0] = new Point(centerX - 50, centerY + 50); line1[1] = new Point(centerX + 50, centerY + 50); line2 = new Point[2]; line2[0] = new Point(centerX + 50, centerY + 50); line2[1] = new Point(centerX + 50, centerY - 50); line3 = new Point[2]; line3[0] = new Point(centerX + 50, centerY - 50); line3[1] = new Point(centerX - 50, centerY - 50); line4 = new Point[2]; line4[0] = new Point(centerX - 50, centerY - 50); line4[1] = new Point(centerX + 50, centerY + 50); } protected void onDraw(Canvas canvas) { } }

We've still not actually drawn anything, but we've defined four lines as a set of two points each. We can use the drawLine method of the _canvas object to actually draw the lines on the bitmap. Each point (in each line) has an x and y property: that's the beauty of using the Point class to define our lines. Again, we'll use a separate method for this: private void drawSquare() { _canvas.drawLine(line1[0].x, line1[0].y, line1[1].x, line1[1].y, paint); _canvas.drawLine(line2[0].x, line2[0].y, line2[1].x, line2[1].y, paint); _canvas.drawLine(line3[0].x, line3[0].y, line3[1].x, line3[1].y, paint); _canvas.drawLine(line4[0].x, line4[0].y, line4[1].x, line4[1].y, paint); invalidate(); }

263 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

There are quite a few method signatures for drawLine. This one takes the starting x and y coordinates of the line, the ending x and y coordinates, and the paint object to apply when drawing. We get the coordinate values from each line's Point's x and y properties, and of course, we already have the paint object. At the end of the method, we call invalidate to tell the view to redraw itself. We'll call makeSquare and drawSquare in the onMeasure method: We're almost done! At this point, we've set up the view to the full size of the screen, specified the starting and ending points of each line of the square, and actually drawn the square on the canvas' bitmap. All that remains is to display the bitmap on the screen, which we will do in onDraw: protected void onDraw(Canvas canvas) { canvas.drawBitmap(_bitmap, 0, 0, paint); }

Note that we're using the canvas parameter of onDraw to draw the bitmap, rather than the _canvas property. Four arguments are needed: the bitmap to draw on the view's canvas, the position of the left side of the bitmap relative to the view, the position of the top of the bitmap relative to the view, and the paint object to use. Here's the result:

264 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

It would be nice to be able to set the background color of the view as well. We can do this in a method: int color) { private void paintBackground(int Paint bgPaint = new Paint(); bgPaint.setColor(color); bgPaint.setStyle(Paint.Style.FILL); _canvas.drawRect(0, 0, screenWidth, screenHeight, bgPaint); }

bgPaint can be local to this method, as it is only used to set the background color. We pass in the color value, then set up the bgPaint object with a FILL style. Then we use the drawRect method of the _canvas object to draw a rectangle that takes up the entire screen. Let's call this

265 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

method in onMeasure, before we actually draw the square: protected void onMeasure(int int widthSpec, int heightSpec) { super super.onMeasure(widthSpec, heightSpec); //get the screen width and height: screenWidth = View.MeasureSpec.getSize(widthSpec); screenHeight = View.MeasureSpec.getSize(heightSpec); //set the dimension of the view: setMeasuredDimension(screenWidth, screenHeight); //set up the Bitmap, and use it to create the Canvas: _bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); _canvas = new Canvas(_bitmap); //set the background color: paintBackground(Color.BLACK); //make and draw the square: makeSquare(); drawSquare(); }

266 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

Here is the final version of the GraphicView class: public class GraphicView extends View { int screenWidth; int screenHeight; Canvas _canvas; Bitmap _bitmap; Paint paint; Point line1[]; Point line2[]; Point line3[]; Point line4[];

267 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

public GraphicView(Context context) { super super(context); // TODO Auto-generated constructor stub paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); } int widthSpec, int heightSpec) { protected void onMeasure(int super super.onMeasure(widthSpec, heightSpec); //get the screen width and height: screenWidth = View.MeasureSpec.getSize(widthSpec); screenHeight = View.MeasureSpec.getSize(heightSpec); //set the dimension of the view: setMeasuredDimension(screenWidth, screenHeight); //set up the Bitmap, and use it to create the Canvas: _bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); _canvas = new Canvas(_bitmap); //set the background color: paintBackground(Color.BLACK); //make and draw the square: makeSquare(); drawSquare(); } int color) { private void paintBackground(int Paint bgPaint = new Paint(); bgPaint.setColor(color); bgPaint.setStyle(Paint.Style.FILL); _canvas.drawRect(0, 0, screenWidth, screenHeight, bgPaint); } private void makeSquare() { int centerX = screenWidth / 2; int centerY = screenHeight / 2;

268 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

line1 = new Point[2]; line1[0] = new Point(centerX - 50, centerY + 50); line1[1] = new Point(centerX + 50, centerY + 50); line2 = new Point[2]; line2[0] = new Point(centerX + 50, centerY + 50); line2[1] = new Point(centerX + 50, centerY - 50); line3 = new Point[2]; line3[0] = new Point(centerX + 50, centerY - 50); line3[1] = new Point(centerX - 50, centerY - 50); line4 = new Point[2]; line4[0] = new Point(centerX - 50, centerY - 50); line4[1] = new Point(centerX - 50, centerY + 50); } private void drawSquare() { _canvas.drawLine(line1[0].x, line1[0].y, line1[1].x, line1[1].y, paint); _canvas.drawLine(line2[0].x, line2[0].y, line2[1].x, line2[1].y, paint); _canvas.drawLine(line3[0].x, line3[0].y, line3[1].x, line3[1].y, paint); _canvas.drawLine(line4[0].x, line4[0].y, line4[1].x, line4[1].y, paint); invalidate(); } protected void onDraw(Canvas canvas) { canvas.drawBitmap(_bitmap, 0, 0, paint); } }

There are many more things that we can do using 2D graphics. As a starting point for further learning (and as a help in doing this chapter's 2D graphics exercise...) take a look at the Canvas methods listed at http://developer.android.com/reference/android/graphics/Canvas.html. Well, we have a view, and we have a square. Let's see if we can add some code to detect a touch event and determine whether the touch occurred inside or outside the square.

269 ___________________________________________________________________________________ (c) Eduonix Technologies Pvt Ltd. All rights reserved

9.4 Detecting Touch Events Touch events are fairly easy to handle in Android. Since touch events can only happen on a View or a View subclass, the proper place to detect a touch is in the GraphicView subclass. We'll begin with a simple override of the onTouchEvent method of the View class that logs out whether the touch occurred inside or outside the view: public boolean onTouchEvent(MotionEvent event) { //get the (x, y) of the touch event: int centerX = screenWidth / 2; int centerY = screenHeight / 2; if (event.getAction() == MotionEvent.ACTION_UP) { float touchX = event.getX(); float touchY = event.getY(); if (touchX >= centerX - 50 && touchX = centerY - 50 && touchY
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF