time

Getting started with Google Maps for Android

Getting started with Google Maps for Android

The Google Maps Android API consists of a core set of classes that combine to provide mapping capabilities in Android applications. The key elements of a map are as follows:

  • GoogleMap. The main class of the Google Maps Android API. This class is responsible for downloading and displaying map tiles and for displaying and responding to map controls. The GoogleMap object is not created directly by the application but is instead created when MapView or MapFragment instances are created. A reference to the GoogleMap object can be obtained within application code via a call to the getMap() method of a MapView, MapFragment or SupportMapFragment instance.
  • MapView. A subclass of the View class, this class provides the view canvas onto which the map is drawn by the GoogleMap object, allowing a map to be placed in the user interface layout of an activity.
  • SupportFragmentMap. A subclass of the Fragment class, this class allows a map to be placed within a Fragment in an Android layout.
  • Marker. The purpose of the Marker class is to allow locations to be marked on a map. Markers are added to a map by obtaining a reference to the GoogleMap object associated with a map and then making a call to the addMarker() method of that object instance. The position of a marker is defined via Longitude and Latitude. Markers can be configured in a number of ways, including specifying a title, text and an icon. Markers may also be made to be draggable, allowing the user to move the marker to different positions on a map.
  • Shapes. The drawing of lines and shapes on a map is achieved through the use of the Polyline, Polygon and Circle classes.
  • UiSettings. The UiSettings class provides a level of control from within an application of which user interface controls appear on a map. Using this class, for example, the application can control whether or not the zoom, current location and compass controls appear on a map. This class can also be used to configure which touch screen gestures are recognized by the map.
  • My Location Layer. When enabled, the My Location Layer displays a button on the map which, when selected by the user, centers the map on the user’s current geographical location. If the user is stationary, this location is represented on the map by a blue marker. If the user is in motion the location is represented by a chevron indicating the user’s direction of travel.

To use Google Maps you need to create a valid Google Maps API key. The key is free, you can use it with any of your applications that call the Maps API, and it supports an unlimited number of users. You can register your application on the Google Developer Console and enable the API. To do this, go to Get API Key, click GET A KEY and create/enable API for your project.

Next, to test your app you need Android emulator with Google Play services or a compatible Android device that runs Android 2.3 or higher and includes Google Play Store.

If you have Android emulator without Google Play services you will get

W/GooglePlayServicesUtil: Google Play Store is missing

To fix it, create Android emulator in Android Studio with Google APIs, for example, Android 6.0 (with Google APIs).

After that you can get :)

Google Play services out of date. Requires 9683000 but found 8489270

It means that you have installed new version in build.gradle, but device has old one. I fix it by downgrading version in build.gradle to 8+.

So, you have the key and Android emulator with Google Play services.

Place the following lines into the dependencies node of the build.gradle file.

// for real device
// compile 'com.google.android.gms:play-services:9.8.0'
// for emulator
compile 'com.google.android.gms:play-services:8+'
compile 'com.google.android.gms:play-services-location:8+'

Once you have your libraries imported, you can close build.gradle and open your AndroidManifest.xml file. Above the application node, you need to declare that the application uses OpenGL ES 2.0 and define the permissions needed by your application.

<uses-feature android:glEsVersion="0x00020000" android:required="true"/ >

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  • INTERNET – if we are connected to Internet or not.
  • ACCESS_FINE_LOCATION - to determine user’s location using GPS. It will give us precise location.
  • ACCESS_COARSE_LOCATION – to determine user’s location using WiFi and mobile. It will give us an approximate location.

Within the application node, you need to add metadata Maps API key.

<meta-data android:name="com.google.android.geo.API_KEY" android:value="API_KEY"/>

Next, you need to extend MainActivity by implementing OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnMapLongClickListener. SupportMapFragment is used here rather than com.google.android.gms.maps.MapFragment in order to add backwards compatibility before API 12.

  • ConnectionCallbacks and OnConnectionFailedListener are designed to monitor the state of the GoogleApiClient, which is used in this application for getting the user's current location.
  • OnInfoWindowClickListener is triggered when the user clicks on the info window that pops up over a marker on the map.
  • OnMapLongClickListener and OnMapClickListener are triggered when the user either taps or holds down on a portion of the map.
  • OnMarkerClickListener is called when the user clicks on a marker on the map, which typically also displays the info window for that marker.

MapFragment is a map component in an app. This fragment is the simplest way to place a map in an application. It's a wrapper around a MapView of a map to automatically handle the necessary life cycle needs. Being a fragment, this component can be added to an activity's layout file simply with the XML below. Open activity_main.xml from your resources folder and change it so that it includes the fragment as a view.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

Returning to our MainActivity class, you need to define some global values at the top of the class for use in your application.

private GoogleMap map;
private GoogleApiClient googleApiClient;
private Location currentLocation;

Here googleApiClient and currentLocation are used for getting the user's location for initializing the map camera. Next, you need to create your GoogleApiClient and initiate LocationServices in order to get your user's current location. In the moveToMyLocation method, you initialize the camera and some basic map properties. You start by creating a CameraPosition object through the CameraPosition.Builder, with a target set for the latitude and longitude of your user and a set zoom level.

setMyLocationEnabled adds a button to the top right corner of the MapFragment that automatically moves the camera to your user's location when pressed.

setZoomControlsEnabled adds + and - buttons in the lower right corner, allowing the user to change the map zoom level without having to use gestures. There's a few more interesting things that you can set using UiSettings, such as adding a compass or disabling gestures, which you can find in the UiSettings reference.

Alternatively, the map:uiZoomControls property may be set within the map element of the XML resource file: map:uiZoomControls="false".

The compass may be displayed either via a call to the setCompassEnabled() method of the UiSettings instance, or through XML resources using the map:uiCompass property.

onMarkerClick method creates a generic red marker where the user has tapped. Additional options, such as setting a marker as draggable, can be set through the MarkerOptions object. You can find additional attributes in the official MarkerOptions reference.

android_map.png

Full code of MainActivity

public class MainActivity extends FragmentActivity implements OnMapReadyCallback,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        GoogleMap.OnMarkerClickListener,
        GoogleMap.OnMapLongClickListener {

    private GoogleMap map;
    private GoogleApiClient googleApiClient;
    private Location currentLocation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        googleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION ) 
            != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(MainActivity.this, "Please allow ACCESS_COARSE_LOCATION persmission.", 
                Toast.LENGTH_LONG).show();
            return;
        }

        currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);

        map.setMyLocationEnabled(true);
        map.getUiSettings().setZoomControlsEnabled(true);
        map.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
            @Override
            public boolean onMyLocationButtonClick() {
                moveToMyLocation();
                return false;
            }
        });

        moveToMyLocation();
    }

    @Override
    public void onConnectionSuspended(int i) {}

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {}

    @Override
    public void onMapLongClick(LatLng latLng) {
        MarkerOptions options = new MarkerOptions().position(latLng);
        options.title(getAddressFromLatLng(latLng));

        options.icon(BitmapDescriptorFactory.defaultMarker());
        map.addMarker(options);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        map = googleMap;
        LatLng latLng = new LatLng(49.2400, 28.4811);
        MarkerOptions marker = new MarkerOptions()
            .position(latLng)
            .title("Vinnytsia")
            .snippet("My hometown!");
        map.addMarker(marker);
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 18));
        map.setOnMarkerClickListener(this);
        map.setOnMapLongClickListener(this);
    }

    private String getAddressFromLatLng(LatLng latLng) {
        Geocoder geocoder = new Geocoder(MainActivity.this);

        String address = "";
        try {
            address = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1)
                    .get(0).getAddressLine(0);
        } catch (IOException e ) {
        }

        return address;
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        marker.showInfoWindow();
        return true;
    }


    @Override
    protected void onStart() {
        super.onStart();
        googleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();

        if( googleApiClient != null && googleApiClient.isConnected() ) {
            googleApiClient.disconnect();
        }
    }

    public void moveToMyLocation() {
        if (currentLocation != null) {
            CameraPosition position = CameraPosition.builder()
                    .target(new LatLng(currentLocation.getLatitude(),
                            currentLocation.getLongitude()))
                    .zoom(16)
                    .build();

            map.animateCamera(CameraUpdateFactory.newCameraPosition(position), null);
        } else {
            Toast.makeText(this, "Can not get user location!", Toast.LENGTH_LONG).show();
        }
    }
}

Don’t like the default Android pins? You can also create a marker with a custom icon as the pin. Go back to onMapLongClick() and add the following line of code after the MarkerOptions instantiation:

options.icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_user_location)));

Download custom pins named ic_user_location from this link and unzip it to res/mipmap.

You can also change the type of the MAP dynamically by making a call to the setMapType() method of the corresponding GoogleMap object, passing through one of the following values:

  • GoogleMap.MAP_TYPE_NONE. An empty grid with no mapping tiles displayed.
  • GoogleMap.MAP_TYPE_NORMAL. The standard view consisting of the classic road map.
  • GoogleMap.MAP_TYPE_SATELLITE. Displays the satellite imagery of the map region.
  • GoogleMap.MAP_TYPE_HYBRID. Displays satellite imagery with the road maps superimposed.
  • GoogleMap.MAP_TYPE_TERRAIN. Displays topographical information such as contour lines and colors.

Handling map gesture interaction

The Google Maps Android API is capable of responding to a number of different user interactions. These interactions can be used to change the area of the map displayed, the zoom level and even the angle of view (such that a 3D representation of the map area is displayed for certain cities).

Map zooming gestures

Support for gestures relating to zooming in and out of a map may be enabled or disabled using the setZoomControlsEnabled() method of the UiSettings object associated with the GoogleMap instance. For example, the following code enables zoom gestures for our example map:

import com.google.android.gms.maps.UiSettings;
...
UiSettings mapSettings;
mapSettings = mMap.getUiSettings();

The same result can be achieved within an XML resource file by setting the map:uiZoomGestures property to true or false. When enabled, zooming will occur when the user makes pinching gestures on the screen. Similarly, a double tap will zoom in whilst a two finger tap will zoom out. One finger zooming gestures, on the other hand, are performed by tapping twice but not releasing the second tap and then sliding the finger up and down on the screen to zoom in and out respectively.

Map scrolling/panning gestures

A scrolling, or panning gesture allows the user to move around the map by dragging the map around the screen with a single finger motion. Scrolling gestures may be enabled within code via a call to the setScrollGesturesEnabled() method of the UiSettings instance:

UiSettings mapSettings;
mapSettings = mMap.getUiSettings(); 
mapSettings.setScrollGesturesEnabled(true);

Alternatively, scrolling on a map instance may be enabled in an XML resource layout file using the map:uiScrollGestures property.

Map tilt gestures

Tilt gestures allow the user to tilt the angle of projection of the map by placing two fingers on the screen and moving them up and down to adjust the tilt angle. Tilt gestures may be enabled or disabled via a call to the setTiltGesturesEnabled() method of the UiSettings instance, for example:

UiSettings mapSettings;
mapSettings = mMap.getUiSettings(); 
mapSettings.setTiltGesturesEnabled(true);

Tilt gestures may also be enabled and disabled using the map:uiTiltGestures property in an XML layout resource file.

Map rotation gestures

By placing two fingers on the screen and rotating then in a circular motion, the user may rotate the orientation of a map when map rotation gestures are enabled. This gesture support is enabled and disabled in code via a call to the setRotateGesturesEnabled() method of the UiSettings instance, for example:

UiSettings mapSettings;
mapSettings = mMap.getUiSettings(); 
mapSettings.setRotateGesturesEnabled(true);

Rotation gestures may also be enabled or disabled using the map:uiRotateGestures property in an XML layout resource file.

Drawing on the map

The GoogleMap object has a set of methods that make it easy to draw shapes and place images onto the map. To draw a simple circle, you only need to create a CircleOptions object, set a radius and center location, and define the stroke/fill colors and size.

Once you have a CircleOptions object, you can call addCircle to draw the defined circle on top of the map. Just like when placing markers, objects that are drawn on the map return an object of the drawn item type so it can be referenced later if needed.

private void drawCircle(LatLng loc) {
    CircleOptions options = new CircleOptions();
    options.center(loc);

    // radius in meters
    options.radius(10);
    options.fillColor(R.color.colorPrimary);
    options.strokeColor(R.color.colorPrimaryDark);
    options.strokeWidth(10);
    map.addCircle(options);
}

I added drawCircle() to onMapReady() method after map.setOnMapLongClickListener(this);.

To draw a different closed-off shape, you can take multiple LatLng points and create a PolygonOptions object. As you can see below, PolygonOptions are created in a similar fashion to CircleOptions. Instead of using a center and radius method, you use add with a list of points. You can then call addPolygon to draw the shape. For this example, you simply draw a triangle onto the map.

private void drawPolygon(LatLng startPoint) {
    LatLng point2 = new LatLng(startPoint.latitude + .001, startPoint.longitude);
    LatLng point3 = new LatLng(startPoint.latitude, startPoint.longitude + .001);

    PolygonOptions options = new PolygonOptions();
    options.add(startPoint, point2, point3);

    options.fillColor(R.color.colorPrimary);
    options.strokeColor(R.color.colorPrimaryDark);
    options.strokeWidth(10);

    map.addPolygon(options);
}

Useful links

comments powered by Disqus