public class DemoMapAppActivity
extends FragmentActivity
Related Information:
Developer Guide:The cornerstone of this demo is the Google Maps Android API v2 (link above) which is used to integrate maps into a mobile UI. Developing an app using Google Maps requires the development environment to include the Google Play services SDK. As well, each app — whether a simple demo or an app distributed on Google Play — requires the manifest to include a Google Maps API key. The key enables access to the Google Maps servers. The details for setting up the development environment and obtaining the API key are described in the Developer Guide (link above). Follow the instructions beginning at Getting Started. A summary version of the same instructions is found in the Quick Start Guide (link above). Once the development environment is setup and a project is created and configured with the API key, it is remarkably simple to include a map in a UI. Many complex operations are handled automatically by the API. The app developer can focus on the UI while avoiding the messy details of accessing Google Maps servers, downloading data, or caching map tiles. The
- Google Maps Android API v2 (and all links within)
Google Services:
Reference:
Training:
Quick Start Guide:
GoogleMap
class models the map object within the app. Within the UI, a map is
represented either by a MapFragment
or a MapView
. This demo uses a
variant of MapFragment
called SupportMapFragment
, a wrapper class that
handles a map view and the necessary life cycle needs. Since it is a fragment, it is added
directly to the activity's layout file, in this case, main.xml
:
<fragment android:id="@+id/map" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" class="com.google.android.gms.maps.SupportMapFragment" />The main activity extends
FragmentActivity
, which is the base class for activities
that use the support-based Fragment
API. Note above that the Id for the
SupportMapFragment
is map
. The map itself is declared and then
retrieved in activity's onCreate
method:
GoogleMap myMap; ... myMap = ((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map)).getMap();Thereafter, it is a simple matter of listening for UI events and then manipulating the map through methods of the
GoogleMap
class and related classes such as
CameraPosition
.
Upon launch, Demo MapApp presents a map of Ontario (below left). The UI includes four buttons
along the top to directly change to a new map. Along the bottom there are UI elements for the
camera control mode (discussed shortly). Within the map, there is a marker in the center of
Ontario, a my-location marker in the Toronto area, zoom buttons at the bottom right, and a seek
button at the top right. (Note: Location sensing must be enabled on the device: Settings >
Location > On.) Tapping the "York U" button changes the map to a close up of York University
(below center). The transition is smoothed using animation.
CameraPosition
object. The parameters are as follows:
Camera View Parameter | Data Type | Description |
---|---|---|
Target | LatLng
| Sets the location the camera is pointing at. The target location is specified through a
LatLng object which holds the latitude and longitude of the location.
|
Zoom | float
| Sets the zoom level of the camera. Zoom ranges from 0 to 21. At zoom = 0, the entire world is rendered with a width of 256 dp (density independent pixels). Adding 1 to zoom doubles the width. |
Bearing | float
| Sets the direction the camera is pointing, in degrees clockwise from north. North is 0 degrees (= 360 degrees). South is 180 degrees. |
Tilt | float
| Sets the angle, in degrees, of the camera from the nadir (directly facing the Earth). The minimum is 0 (looking directly down). The maximum ranges from 30 to 67.5, depending on the current zoom. (Click here to view the algorithm for max tilt.) |
CameraPosition
and
CameraPosition.Builder
and in the topic Changing the View in
the Google Maps Android API v2 Developer's Guide.
As examples, the views for the Ontario and York University maps above are hard-coded in finals:
final static CameraPosition ONTARIO = new CameraPosition.Builder().target(new LatLng(50.007475, -85.954709)) .zoom(4f).tilt(0f).bearing(0f).build(); final static CameraPosition YORK_UNIVERSITY = new CameraPosition.Builder().target(new LatLng(43.7731, -79.5036)) .zoom(15.5f).tilt(50f).bearing(300f).build();So, the Ontario map has zoom = 4 and the York U map has zoom = 15.5. Note that the camera position for the York U map has a tilt of 50 degrees from the nadir and a bearing = 300 degrees from North. These properties are evident in the map. The actual change in the map view is coded in the button callback with a call to
changeCamera
:
changeCamera(CameraUpdateFactory.newCameraPosition(buttons[idx]), true); ... private void changeCamera(CameraUpdate update, boolean animate) { if (animate) myMap.animateCamera(update); else myMap.moveCamera(update); }Using
animateCamera
creates a smooth animated transition to the new map view.
Camera Control Mode
To further illustrate the zoom, tilt, and bearing parameters for a camera position, the demo
includes a camera control mode. This is implemented along the bottom of the UI using (i) a
button to display and change the mode, (ii) a seekbar to the adjust the value of a parameter, and
(iii) a text field to display the current value (below left). Tapping the button pops up a dialog
to change the camera control mode (below right).
onProgressChanged
:
@Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { CameraPosition cp = myMap.getCameraPosition(); float zoom = cp.zoom; float tilt = cp.tilt; float bearing = cp.bearing; switch (cameraControlMode) { case ZOOM: zoom = (float)progress / SCALE_FACTOR; tilt = Math.min(tiltSave, getMaxTilt(zoom)); break; case TILT: tilt = (float)progress / SCALE_FACTOR; tiltSave = tilt; break; case BEARING: bearing = progress; } changeCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(cp.target, zoom, tilt, bearing)), false); updateSeekValue(progress, cameraControlMode); }First, the current camera zoom, tilt, and bearing are retrieved. Depending on the current camera control mode, one of these values is updated based on the position (aka progress) of the slider. (Since the maximum tilt depends on zoom, tilt is also adjusted in zoom mode, if necessary.) Then, the new camera position is passed to
changeCamera
as described earlier. Note
that the boolean argument to changeCamera
is false
. Thus, the camera
change is direct, rather than animated. This creates a more responsive experience as the user
drags the seekbar slider.
Navigation Drawer
Besides four buttons at the top of the UI, the demo includes several additional pre-defined
camera views, or bookmarks. These are accessed using a Navigation Drawer – a sliding panel
at the left edge of the UI. The panel is revealed when the user swipes a finger from the left
edge of the screen:
CameraPosition
objects for bookmarks are stored in a
different array. As well, the code is responding to a different callback. The navigation drawer's
listener method is onItemClick
:
@Override public void onItemClick(AdapterView> parent, View view, int position, long id) { changeCamera(CameraUpdateFactory.newCameraPosition(bookmarks[position]), true); myMap.addMarker(new MarkerOptions().position(bookmarks[position].target).title(bookmarkNames[position])); tiltSave = bookmarks[position].tilt; myDrawerLayout.closeDrawer(myDrawerList); }Using a navigation drawer in an Android UI is straight forward. The primary setup is in the XML layout file which uses as
DrawerLayout
view group at the top level. The organization
in the demo closely follows the example in the Training document "Creating a Navigation Drawer"
(link above). Consult for details.
As an example of using the navigation drawer, note that the last item in the navigation drawer
menu is Sochi – the location of the 2014 Winter Olympics. Tapping Sochi advances the camera
position to this Russian resort town on the coast of the Black Sea (below left). Navigations here
might include zooming in to the Sochi Olympic Park (below center) and then switching the camera
control mode to Tilt and adjusting the camera tilt (below right).
myMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);Of course, the code executes in response to the Settings selection (from
onOptionsItemSelected
). The implementation of this feature is a bit involved since
map type (satellite vs. normal) is implemented as a setting – a value set by the
user that persists from one invocation of the app to another. So, selecting Settings, launches a
settings activity, etc. See Demo_Settings for further discussion.
Note that the Options Menu also includes entries for Bookmarks, Restore, and Help. Bookmarks
provides an alternative method to open the navigation drawer. Restore clears the map and brings
up the original map of Ontario. Help is unimplemented but selecting Help is acknowledged using
popup Toast (below left).
GoogleMap.OnMapLongClickListener
. If a long press
is detected, a 50-ms vibrotactile pulse is emitted along with popup Toast acknowledging the
long-press (above right). Perhaps you can think of a useful UI response for a long-press.
Custom Markers
It is relatively easy to add custom markers to a map. An example is the marker for the Lassonde
Building mentioned earlier. The marker was added to the map during the initial setup:
lassonde = myMap.addMarker(new MarkerOptions().alpha(0f).position(LASSONDE_BUILDING).title("Lassonde Building") .snippet("Dept. of EECS").icon(BitmapDescriptorFactory.fromResource(R.drawable.lassonde_marker_small)));Of course, a marker image must be created and saved as a resource. In this case, the marker image is stored in
lassonde_marker_small.png
. Different resolutions are recommended to
accommodate different display densities. For example, an 18 × 18 version of the marker
image is stored in res/drawable-hdpi/
and a 36 × 36 version is stored in
res/drawable-mdpi/
. The drawable directories with designations
hdpi
and mdpi
are for "high" and "medium" density
displays, respectively. See Supporting Multiple
Screens in the API Guides for additional discussion.
Note in the code above that alpha is set to 0. Thus, the marker is invisible. The marker is only
made visible if the zoom level is 15 or greater. This is done in the onCameraChange
method. Animation is included to create a fade-in effect:
if (position.zoom >= 15) ObjectAnimator.ofFloat(lassonde, "alpha", 1f).setDuration(1000).start(); else lassonde.setAlpha(0f);There is likely a better way to manage markers if the app includes many custom markers. This is not explored here.
Constructor and Description |
---|
DemoMapAppActivity() |
Modifier and Type | Method and Description |
---|---|
protected void |
onActivityResult(int requestCode,
int resultCode,
Intent data) |
void |
onButtonClick(View v) |
protected void |
onCreate(Bundle savedInstanceState) |
boolean |
onCreateOptionsMenu(Menu menu) |
boolean |
onOptionsItemSelected(MenuItem item) |
protected void |
onResume() |
protected void onCreate(Bundle savedInstanceState)
protected void onResume()
public boolean onCreateOptionsMenu(Menu menu)
public boolean onOptionsItemSelected(MenuItem item)
protected void onActivityResult(int requestCode, int resultCode, Intent data)
public void onButtonClick(View v)