Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition4.2. Initialization in MicroJobs.javaHaving seen the
XML resources that Android uses to launch the application, we can turn to
some Java code that initializes the application. Use Eclipse to open
MicroJobs.java
in the Java editor. After the package declaration and the import statements, the
MicroJobs class is defined. Most Activities (and the
other activities in this application) extend the Activity class. Because we want to display a map in this application, and
we want to take advantage of the powerful mapping features built into
Android, we declare that MicroJobs will extend MapActivity, as shown in
the following code segment. If you look in the Android documentation for
MapActivity, you will see that it is a subclass of Activity, and so
inherits all the Activity methods and variables: /**
* MicroJobs
*/
public class MicroJobs extends MapActivity {Skip over the first few variables and the definition of the
MJOverlay class for the moment, to get to the
definition of the onCreate method, as
shown in the code block that follows. This is the method called by Android
when it first launches an application, so that's where we'll put our
initialization code. Let's take a look at it, section by section: MapView mvMap;
MicroJobsDatabase db;
MyLocationOverlay mMyLocationOverlay;
double latitude, longitude;
/**
* Called when the activity is first created.
*
* @see com.google.android.maps.MapActivity#onCreate(android.os.Bundle)
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);The first thing to note is that onCreate receives
an argument when it runs: a Bundle that will be referred to as savedInstanceStte. Note also that the first
thing onCreate does is call the
onCreate method of its superclass. That
makes sense because we want the chain of superclasses to initialize
themselves appropriately. But what is this Bundle thing? A Bundle is one of the mechanisms used by Android to pass structured
data between Activities. It's just a parcel of key/object pairs,
and you'll see later when we start another Activity that we have the
option of passing that Activity a Bundle. In the case of MicroJobs, we aren't going to make use of
any of the resources in the savedInstanceState Bundle, but we
faithfully pass it on to the onCreate
method of our superclass. The very last line in this section of code sets our Content View. A
view, as we explained in Chapter 1,
describes how an application window appears and interacts with the user.
So the setContentView call tells
Android that we want to use the layout information in R.layout.main.java to lay out the screen for
the Activity. As Chapter 2 explained,
the R.* resource files
are generated by the Android SDK from your own XML resource files when you
compile your application (as a result of selecting Run); in this case, the
parameters come from our res/layout/main.xml file. Android "inflates"
these parameters when layouts are created, using them to determine how the
layout looks. So let's digress for a minute and take a look at the first part of
the XML version of that file: <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffc5d1d4"
>
<com.google.android.maps.MapView
android:id="@+id/mapmain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="0P18K0TAE0dO2GifdtbuScgEGLWe3p4CYUQngMg"
/>
<TextView
android:id="@+id/lblMicroJobsToday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MicroJobs for You Today Near:"
android:textSize="20dp"
android:textColor="#FF000000"
android:layout_centerHorizontal="true"
android:gravity="top"
/>
<Spinner
android:id="@+id/spnLocations"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="2dp"
android:layout_below="@+id/lblMicroJobsToday"
/>
<Button
android:id="@+id/btnShowList"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="List Jobs"
android:textSize="20sp"
android:gravity="center_vertical"
android:layout_centerInParent="true"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
First, we say that we are going to use a Relative Layout for this
screen. Android offers a variety of Layout types, and though it's beyond
the scope of this book, you can even define your own Layout types. A
Relative Layout says that we are going to define the positions of the
different user interface elements by relating their positions to each
other and to the overall screen. That may sound a little vague right now,
but it will be clear when we go into some of the attributes in detail. We
go into much more depth on the process of screen layout later in this book
in Chapter 12. The first few lines of code define overall attributes for the screen
layout:
android:orientation This tells Android which way we want "gravity" to work in
determining the screen layout.
android:layout_width and
android:layout_height These tell Android that we want to make use of the whole screen;
we aren't trying to leave room for other Activities to be partially
visible.
android:background This defines the color of the background for the
application (which isn't really visible in our case, since the map
covers the whole screen).
This is a good time to talk briefly about defining colors in Android;
we'll discuss it further when we talk about graphics in Chapter 12. The color
specification will be familiar to you if you've worked with web pages
(although on a web page, the Alpha value is the last item instead of the
first). Colors in Android are defined by a pound sign (#)
followed by four 8-bit integers in hexadecimal:
Alpha The transparency of the resulting color, FF being completely
opaque and 0 being completely transparent.
Red Red's contribution to the resulting color, FF being fully on
and 0 meaning no red.
Green Green's contribution to the resulting color, FF being fully
on and 0 meaning no green.
Blue Blue's contribution to the resulting color, FF being fully
on and 0 meaning no blue.
Common colors are also defined as global constants for use in
Java. |
The rest of the file defines each of the visual elements of the
screen, and tells Android where we'd like it placed. The following elements of the application are defined in the
file:
Section starting
<com.google.android.maps.MapView This is the main View for this Activity:a Map that consumes most of the screen and shows the
locations of jobs that might be of interest to the user. You'll see
that most Views can be described in a layout file by just writing
the name of the View, but this holds only for Views that are part of
Android's default libraries. MapViews are not included, so we create
an XML element for it. The MapView View is defined in the maps
library, so the full pathname is com.google.android.maps.MapView. We assign
it the following attributes:
android:id This defines an identifier that we can use to refer to
this View, either from other places in this XML file
or from our Java code. You'll see later in the Java
initialization code that we connect the Java source code with
this XML source through these IDs.
android:layout_width
and android:layout_height These are the same attributes defined earlier for the
application, but here they apply to the MapView alone, not the
whole application. The fill_parent value, as its name
suggests, asks for permission to fill all the space
within the parent. In this case the parent happens to be the
whole screen, but it is important to keep in mind that
this attribute affects only the relationship between the
MapView and its parent.
android:clickable This tells Android that we want an interactive MapView that the
user can click on using the touchscreen on the Android phone
(simulated by mouse clicks on the emulated Android
phone).
android:apiKey This is an attribute unique to MapViews. You need an API Key from
Google to use a Map View, just as you do when you add a Google
map to your web page. You'll see how to obtain and use Map API
Keys in Chapters Chapter 7 and Chapter 9.
Section starting <TextView This will display a Label telling the user what he's looking
at. The attributes defined here are typical of what needs to be
defined for a TextView. In addition to attributes we already saw
under MapView, this element has:
android:text This contains the text we'd like to display in the
TextView.
android:textSize This says how big Android should display the text—in
this case, 20 scaled pixels high (see the upcoming sidebar for
a description of Android dimensions).
android:textColor This defines the color of the text.
android:layout_centerHorizontal This tells Android that we want it to center the displayed
text horizontally.
android:gravity This tells the Android layout manager where to
position the element vertically relative to its container,
when the element is smaller. Gravity can be defined as
top, center_vertical, or bottom. Note that gravity and
attributes like layout_centerHorizontal
are layout hints that the layout manager uses to lay
out the children of a container. There is no guarantee that
the hints will be followed, but the layout manager attempts to
satisfy the combined requests from the container, the children
it contains, and any global layout hints from the user
interface.
There are many other attributes we could define for our
TextView, and they are all described in the Android documentation
that accompanies the SDK.
Section starting <Spinner This is a standard Android control that allows the user to
select from the current location or any of several "favorite"
locations that are recorded in the user's profile. In addition to
the attributes we've seen already, the android:layout_below attribute controls
the placement of the Spinner. This is the first attribute we've seen
that applies specifically to the Relative Layout we chose at the top
of the file. It tells Android that it should position this Spinner
just below the interface element whose id is
lblMicroJobsToday.
Section starting <Button The final segment of main.xml defines a
Button widget, which is just what it sounds like—a button that the
user can press to initiate some action. In this case, we want a
button that takes us to the listing of jobs.
android:layout_width
and android:layout_height These are the same attributes used for the other views,
but we don't want the Button to take up the whole width of the
screen, so we give it a defined width. Vertically, we just
tell it to wrap the text that it is displaying.
android:text This places a label on the Button.
android:textSize This tells Android how large we'd like that text drawn—in this case,
20 scaled pixels.
android:layout_centerInParent Since the button is not as wide as the parent (the
screen), we need to tell the layout manager where to put the
Button horizontally. This says "put it in the middle."
android:layout_alignParentBottom The Button is only tall enough to wrap the label
that it displays, so we also need to tell the layout manager
where to place it vertically on the screen. This says "put it
at the bottom." Note that we could also have said android:gravity=bottom.
Android provides multiple ways of expressing our layout
requests.
Often you will need to specify a dimension for some element of the user interface. In the example
here we generally used scaled pixels (abbreviated "sp"), but Android
actually offers a rich set of dimensions to choose from:
px (pixels) If a dimension is set at 10px, it will be exactly 10 pixels long,
no matter what the physical size (and physical density) of the
pixels on the display. 10px will therefore be different sizes on
handset displays with different pixel densities. On a QVGA
display, for example (320x240 pixels), it will be 1/24th of the
height of the display. The same 10px running on a VGA display
(640x480 pixels) will be 1/64th of the height of the
display.
dip or dp (device-independent pixels) In an effort to make it easier to adapt applications
to different pixel densities, dimensions can be expressed in
device-independent pixels (sometimes also called
"density-independent pixels"). When you specify a dimension of
10dpi, Android will scale the resulting object so it appears the
same size it would appear on a 160dpi screen. For example, if a
640x480 display is 4"x3", its pixel density is 160 dots per inch
(640/4, or 480/3). On that screen, dp's are the same as px's. But
if we run the same application on a tablet-size VGA screen—say,
8"x6"—the pixel density is 80dpi, and a dimension given as 10dp
will be twice as large as a dimension given as 10px. The scaling
factor for dp's is approximate—Android doesn't try to make dp's
come out exactly right.
sp (scaled pixels) Scaled pixels are a lot like dp's, but they are intended
for elements that need finer control over the density scaling
factor, such as text.
pts (points) This is used to express text size in points, just as you would
in any text editor. Points are a fixed dimension (roughly 1/72nd
of an inch), so the text will appear the same size on any
display.
in (inches) This is just what it says: the dimension in
inches.
mm (millimeters) This is also just what it says, only metric this
time.
|
4.2.1. More Initialization of MicroJobs.javaThe previous section was a rather long digression into XML Layout files, but as you
can see, that is where a lot of the initialization of the application's
user interface takes place: where views are defined, named, and given
attributes; where the screen is layed out; and where hints are given to
the layout manager describing the way we would like the screen to look.
Let's get back to the Java code that brings up the application, starting
where we left off in MicroJobs.java: db = new MicroJobsDatabase(this);
// Get current position
final Location myLocation
= getCurrentLocation((LocationManager) getSystemService(Context.LOCATION_SERVICE));
Spinner spnLocations = (Spinner) findViewById(R.id.spnLocations);
mvMap = (MapView) findViewById(R.id.mapmain);
// get the map controller
final MapController mc = mvMap.getController();
mMyLocationOverlay = new MyLocationOverlay(this, mvMap);
mMyLocationOverlay.runOnFirstFix(
new Runnable() {
public void run() {
mc.animateTo(mMyLocationOverlay.getMyLocation());
mc.setZoom(16);
}
});
Create the database object We said before that we are going to use a small SQLite database to hold
the job, worker, and employer information. The first line
initializes that database by asking Android to create a new
MicroJobsDatabase object (and initialize it).
The Java code for this is in the file MicroJobsDatabase.java, and we'll look
at it in detail later in Chapter 8.
Get our location We'll need to know our current location to do things like
finding jobs that are close by, so we get it here by calling
getCurrentLocation, which is a
method defined later and that accepts the name of our
LocationManager as its argument. The
LocationManager is a special class that Android
instantiates for you, and you can retrieve the instance for your
application through the call to getSystemService.
Initialize the Spinner As explained in the previous section, we place a Spinner
widget at the top of the screen to help users quickly go to one of
their favorite locations and look for jobs. This is the first time
we encounter the findViewById
method, which is the way we access the IDs we defined in the XML
layout file. If you recall, we identified the Spinner in main.xml as spnLocations. When we built the
application, Android compiled that XML into a Java identifier that
it placed in R.layout.main.java and linked it into
the application. So now we can use findViewById to connect our Java Spinner
to the XML attributes we defined.
Initialize the MapView and
MapController Similarly,we connect the Java MapView to the attributes
defined for it in main.xml, and then attach a
MapController to it. You'll see much more about the controller in
Chapter 9, but for now think of it
as a handle to get to all the methods you need to control the
MapView.
Initialize the LocationOverlay We want to create a LocationOverlay
that will build and draw the Map in our
MapView when we want to view a map of our local
area. Again, Maps are covered in much more detail later, but you
can see here that we use the constructor to create a new overlay
and tell it to run when it gets its first fix from the
LocationManager, so that it displays our current location. We also
set the zoom level so it's about right for a metropolitan
area.
We'll skip over the map overlay initialization, because that will
be covered in more detail in Chapter 9,
where we talk about mapping. We still need to initialize the remaining
Views on this screen: the Button and the
Spinner. The code for these follows: // Create a button click listener for the List Jobs button.
Button btnList = (Button) findViewById(R.id.btnShowList);
btnList.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(MicroJobs.this.getApplication(),
MicroJobsList.class);
startActivity(intent);
}
});
// Load a HashMap with locations and positions
List<String> lsLocations = new ArrayList<String>();
final HashMap<String, GeoPoint> hmLocations = new HashMap<String, GeoPoint>();
hmLocations.put("Current Location", new GeoPoint((int) latitude, (int) longitude));
lsLocations.add("Current Location");
// Add favorite locations from this user's record in workers table
worker = db.getWorker();
hmLocations.put(worker.getColLoc1Name(), new GeoPoint((int)worker.getColLoc1Lat(),
(int)worker.getColLoc1Long()));
lsLocations.add(worker.getColLoc1Name());
hmLocations.put(worker.getColLoc2Name(), new GeoPoint((int)worker.getColLoc2Lat(),
(int)worker.getColLoc2Long()));
lsLocations.add(worker.getColLoc2Name());
hmLocations.put(worker.getColLoc3Name(), new GeoPoint((int)worker.getColLoc3Lat(),
(int)worker.getColLoc3Long()));
lsLocations.add(worker.getColLoc3Name());
ArrayAdapter<String> aspnLocations
= new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item,
lsLocations);
aspnLocations.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spnLocations.setAdapter(aspnLocations);
Create a callback for the btnList Button
View We first get a handle on the Button View by doing a
lookup on its ID, just as we did before for the
Spinner and MapView. We then
set the behavior of the Button, which uses a construct known as a
listener to respond to external
events. When a user clicks a button, Android sends an event to its
OnClickListener
listener. In this code, we set the Button's behavior by
setting its OnClickListener to
the method that we immediately define, onClick. When the user clicks on btnList, we want to
display a list of available MicroJobs. To do that, we have to
launch a new Activity, MicroJobsList.java, which contains the
screen that displays the list. We can do that by calling the
startActivity method with an
Intent that describes the new Activity. The first statement in
onClick() creates the Intent, using the
constructor for Intents that allows us to explicitly name the
Activity. This constructor takes two arguments: a pointer to the
context of the current application, and the name of the Class to
start. The next statement in onClick() then
uses that Intent to start an instantiation of
MicroJobsList.
Initialize the list of entries in the Spinner View We need two data structures to pass to our Spinner: a list
of favorite locations that
the Spinner will display (and the user can select), and a
hash map connecting location names to geographical
locations (latitude and longitude). Don't confuse the HashMap
with a geographical Map; the HashMap uses the term "map"
in the way many programmers use it, to mean an associative
array. We first create the list of location names
(lsLocations), and then the HashMap that we'll use to
map names to GeoPoints (hmLocations). We then put the
first entry, Current Location, into the list and the
HashMap. This entry will always return the user to the current
location. This item is special because it can be a moving target.
For example, the user may be consulting our application on a
fast-moving train or an airplane, so we have to dynamically
retrieve the location of the device whenever the current location
is selected. We then add three entries for the user's "favorite
locations," recorded in the user's record in the workers table in
the MJAndroid database. We'll dive into the details of how the
database works and how it's set up later. For now, we'll just say
that the code immediately following worker =
db.getWorker(); loads the location names and positions
(latitudes and longitudes) into the lsLocations and
hmLocations
lists. Spinner Views require an ArrayAdapter to feed them the list,
so we create one named
aspnLocations, attaching it to the list of location
names in its constructor. Then, we attach the adapter to the
Spinner by calling setAdapter.
The statement
"aspnLocations.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);"
provides the Spinner with the drop-down layout necessary for the
user to display the whole list of locations. Now that we have initialized the lists, we can add the
following code, which enables the appropriate action when the user
clicks on an item with the Spinner: // Set up a callback for the spinner
spnLocations.setOnItemSelectedListener(
new OnItemSelectedListener() {
public void onNothingSelected(AdapterView<?> arg0) { }
public void onItemSelected(AdapterView<?> parent, View v, int position,
long id) {
TextView vt = (TextView) v;
if ("Current Location".equals(vt.getText())) {
latitude = myLocation.getLatitude();
longitude = myLocation.getLongitude();
mc.animateTo(new GeoPoint((int) latitude, (int) longitude));
} else {
mc.animateTo(hmLocations.get(vt.getText()));
}
mvMap.invalidate();
}
});
}
Initialize the Spinner callback Just as we did with the Button View, we create a method
named onItemSelected and set it
to be called when the user selects an item using the Spinner. The
onNothingSelected method is
also required, but we leave it empty (not used). As mentioned earlier, Current
Location is a special case because we retrieve the
device's location dynamically when the user selects that item. The
if block handles that case: we
look to see whether the selection is Current Location and if it is, we get
the current location and go there. Otherwise, we go to the
selected location. Then, in the final statement, we invalidate the map so it
will redraw itself.
|