Presentation Getting around with Google Maps Android API v2

As part of the Google Play services - a platform that offers a better integration with Google products - Google recently released perhaps one of the most frequently requested upgrade: the Google Maps Android API v2. This new framework allows you to embed standard or customized maps directly in your application. Now Google Maps Android API v2 lets you leverage Google's beautiful new vector based maps. In this session, we will learn about the new API and features introduced with Google Maps Android API v2 and how to start visualizing information geographically within your applications.

Speakers


PDF: slides.pdf

Slides

Getting around with

Getting around with GOOGLE MAPS ANDROID API V2

@ CYRILMOTTIER

@ CYRILMOTTIER

I once made a

I once made a LOVELY & GOOD-LOOKING mobile app

I once made a

I once made a LOVELY & GOOD-LOOKING mobile app

IT


IT
 LOOKED LIKE THIS Myapp 1001 - Lorem ipsum amet Lorem ipsum amet 1002 - Lorem ipsum amet Lorem ipsum amet 1003 - Lorem ipsum amet Lorem ipsum amet 1004 - Lorem ipsum amet Lorem ipsum amet 1005 - Lorem ipsum amet Lorem ipsum amet 1006 - Lorem ipsum amet Lorem ipsum amet 1007 - Lorem ipsum amet Lorem ipsum amet 1008 - Lorem ipsum amet Lorem ipsum amet 1009 - Lorem ipsum amet Lorem ipsum amet

I started to do some quick

I started to do some quick USER TESTING

“That’s a pretty

“That’s a pretty nice list of thingy things” – John Doe

“It sucks”

“It sucks” – John Doe’s translator

Lists-based screens

Lists-based screens are boring and give no geographic context

Maps give the user some clear context on

Maps give the user some clear context on POI LOCATION

Maps give the user some clear context on

Maps give the user some clear context on POI LOCATION USER LOCATION

Maps give the user some clear context on

Maps give the user some clear context on POI LOCATION USER LOCATION DISTANCES

Maps give the user some clear context on

Maps give the user some clear context on POI LOCATION USER LOCATION DISTANCE DIRECTIONS

Maps give the user some clear context on

Maps give the user some clear context on POI LOCATION USER LOCATION DISTANCES DIRECTIONS

GOOGLE MAPS

GOOGLE MAPS ANDROID API V2 TO THE RESCUE

Myapp

Myapp Lorem ipsum park Map-based apps m t Lorem ipsum mall Lorem ipsum amet are immersive re Lo um ips e am

Myapp

Myapp Lorem ipsum park Map-based apps m t and that’s awesome :) Lorem ipsum mall Lorem ipsum amet are immersive re Lo um ips e am

1

1 Introducing MAPS API V2

v1

v1 vs v2

v1

v1 vs v2

v1

v1 ed t ca re p e D vs v2

EXTREMELY

EXTREMELY STRAIGHTFORWARD API

Initial [Support]MapFragment setup

Initial [Support]MapFragment setup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestActivity extends FragmentActivity {! private GoogleMap mMap;! ! @Override! protected void onCreate(Bundle savedInstanceState) {! super.onCreate(savedInstanceState);! setContentView(R.layout.activity_marker);! setUpMapIfNeeded();! }! ! @Override! protected void onResume() {! super.onResume();! setUpMapIfNeeded();! }! ! private void setUpMapIfNeeded() {! if (mMap == null) {! mMap = ((SupportMapFragment) getSupportFragmentManager().! findFragmentById(R.id.map)).getMap();! if (mMap != null) {! setUpMap();! }! }! }! ! private void setUpMap() { /* TODO */ }! }

Annotating with Markers

Annotating with Markers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static final LatLng LYON = new LatLng(45.764043, 4.835659);! ! private Marker mMarker;! ! private void setUpMap() {! mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LYON, 6));! ! mMarker = mMap.addMarker(new MarkerOptions().! position(LYON).! title("Lyon").! snippet("City of Lights").! icon(BitmapDescriptorFactory.! defaultMarker(BitmapDescriptorFactory.HUE_RED)));! }

Annotating with Markers

Annotating with Markers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static final LatLng LYON = new LatLng(45.764043, 4.835659);! ! private Marker mMarker;! ! private void setUpMap() {! mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LYON, 6));! ! mMarker = mMap.addMarker(new MarkerOptions().! position(LYON).! title("Lyon").! snippet("City of Lights").! icon(BitmapDescriptorFactory.! defaultMarker(BitmapDescriptorFactory.HUE_RED)));! }

Drawing shapes on MapView

Drawing shapes on MapView 3SHAPES POSSIBLE

Drawing shapes on MapView

Drawing shapes on MapView 3SHAPES POSSIBLE Polyline

Drawing shapes on MapView

Drawing shapes on MapView 3SHAPES POSSIBLE Polyline | Polygon

Drawing shapes on MapView

Drawing shapes on MapView 3SHAPES POSSIBLE Polyline | Polygon | Circle

Drawing shapes on MapView

Drawing shapes on MapView 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void setUpMap() {! mMap.addCircle(getCircleOptions(new LatLng(45.8, 4.8), 20000));! mMap.addCircle(getCircleOptions(new LatLng(46.03, 4.6), 10000));! mMap.addCircle(getCircleOptions(new LatLng(46.03, 5), 10000));! }! ! private CircleOptions mCircleOptions = new CircleOptions();! ! private CircleOptions getCircleOptions(LatLng center,! double radius) {! final float w = 4 * getResources().getDisplayMetrics().density;! return mCircleOptions.! center(center).! radius(radius).! strokeWidth(w).! strokeColor(Color.RED);! }

Overlaying map with GroundOverlay

Overlaying map with GroundOverlay 1 2 3 4 5 6 7 8 9 10 11 private static final LatLng NEWARK = new LatLng(40.71408, -74.22869);! ! private void setUpMap() {! mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(NEWARK, 11));! ! mMap.addGroundOverlay(new GroundOverlayOptions().! image(BitmapDescriptorFactory.! fromResource(R.drawable.newark_1922)).! anchor(0, 1).! position(NEWARK, 8600f, 6500f));! }

Rendering custom tiles with TileOverlay

Rendering custom tiles with TileOverlay 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static final String MOON_MAP_URL_FORMAT =! "http://www.google.moon/bw/%d/%d/%d.jpg";! ! private void setUpMap() {! mMap.setMapType(GoogleMap.MAP_TYPE_NONE);! ! TileProvider tileProvider = new UrlTileProvider(256, 256) {! @Override! public synchronized URL getTileUrl(int x, int y, int zoom) {! String s = String.format(Locale.US,! MOON_MAP_URL_FORMAT, x, y, zoom);! try {! return new URL(s);! } catch (MalformedURLException e) {! throw new AssertionError(e);! }! }! };! ! mMap.addTileOverlay(new TileOverlayOptions().! tileProvider(tileProvider));! }

2

2 Deep dive into THE INTERNALS

Google Maps Android API v2

Google Maps Android API v2 is part of Google Play Services

YOUR APP

YOUR APP

Maps API

Maps API YOUR APP

Maps API

Maps API YOUR APP GMS SERVICE

Maps API

Maps API YOUR APP GMS SERVICE

Maps API

Maps API YOUR APP PLAY STORE GMS SERVICE

EVERYTHING

EVERYTHING EVERYTHING EVERYTHING EVERYTHING IS PARCELABLE EVERYTHING EVERYTHING EVERYTHING

Each call to the API

Each call to the API is a done through a Binder … slo … and it may be w ols w

A VIEW

A VIEW MAY HIDE ANOTHER

TextureView

TextureView API 16+ SurfaceView API 15-

1

1 Only Bitmaps can be rendered 2 No Drawable/View automatic refresh 3 No direct Canvas-based rendering

You must be on the UI THREAD

You must be on the UI THREAD WHEN CALLING ANY METHOD ON any VIEW

You must be on the UI THREAD

You must be on the UI THREAD WHEN CALLING ANY METHOD ON any GOOGLEMAP

3

3 Google Maps Android API v2 TIPS AND TRICKS

Attaching information to Markers

Attaching information to Markers

Attaching information to Markers

Attaching information to Markers

Attaching information to Markers

Attaching information to Markers

Use a MAPPING COLLECTION

Use a MAPPING COLLECTION Map HashMap | LinkedHashMap | ArrayMap

Use a MAPPING COLLECTION

Use a MAPPING COLLECTION Map HashMap | LinkedHashMap | ArrayMap NEW

Attaching information to Markers

Attaching information to Markers 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Map mMarkerPositions = new HashMap();! private Cursor mCursor;! ! private void setUpMap() {! updatePois();! }! ! private void updatePois() {! mMarkerPositions.clear();! if (mMap != null) {! mMap.clear();! if (mCursor != null) {! final MarkerOptions markerOptions = new MarkerOptions();! while (mCursor.moveToNext()) {! final Marker marker = mMap.addMarker(markerOptions.! position(new LatLng(! mCursor.getDouble(PoisQuery.LAT),! mCursor.getDouble(PoisQuery.LNG))));! mMarkerPositions.put(marker, mCursor.getPosition());! }! }! }! }!

17

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 mCursor.getDouble(PoisQuery.LAT),! mCursor.getDouble(PoisQuery.LNG))));! mMarkerPositions.put(marker, mCursor.getPosition());! Attaching information to Markers }! }! }! }! ! private final LoaderManager.LoaderCallbacks mLoaderCallbacks =! new LoaderManager.LoaderCallbacks() {! @Override! public Loader onCreateLoader(int id, Bundle args) {! return new CursorLoader(getBaseContext() /* ... */);! }! ! @Override! public void onLoadFinished(Loader loader, Cursor cursor) {! mCursor = cursor;! updatePois();! }! ! @Override! public void onLoaderReset(Loader loader) {! mCursor = null;! updatePois();! }! };

The “be” attitude

The “be” attitude BE lazy when possible mindful on memory nice with users

1 private void setUpMap() {!

1 private void setUpMap() {! 2 mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {! 3 @Override! 4 public View getInfoWindow(Marker marker) {! 5 return null;! 6 }! 7 ! 8 @Override! 9 public View getInfoContents(Marker marker) {! 10 Integer position = mMarkerPositions.get(marker);! 11 if (position == null || mCursor == null) {! 12 return null;! 13 }! 14 mCursor.moveToPosition(position);! 15 marker.setTitle(mCursor.getString(PoisQuery.TITLE));! 16 marker.setSnippet(mCursor.getString(PoisQuery.SUBTITLE));! 17 return null;! 18 }! 19 });! 20 //...! 21 }

Animate

Animate state transitions

Animate

Animate state transitions

No actual animation support in

No actual animation support in GOOGLE MAPS ANDROID API V2

1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LatLngEvaluator implements TypeEvaluator {! ! @Override! public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {! double endValueLng = endValue.longitude;! ! // Take the shortest path across the 180th meridian.! double lngDelta = startValue.longitude - endValue.longitude;! if (Math.abs(lngDelta) >= 180) {! endValueLng = endValue.longitude + Math.signum(lngDelta) * 360;! }! ! double newLat = eval(fraction, startValue.latitude, endValue.latitude);! double newLng = eval(fraction, startValue.longitude, endValueLng);! return new LatLng(newLat, newLng);! }! ! private static double eval(float fraction, double startValue, double endValue) {! return startValue + fraction * (endValue - startValue);! }! }

SOLUTION

SOLUTION Do it ON YOUR OWN!

1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class LatLngEvaluator implements TypeEvaluator {! ! @Override! public LatLng evaluate(float fraction, LatLng startValue, LatLng endValue) {! double endValueLng = endValue.longitude;! ! // Take the shortest path across the 180th meridian.! double lngDelta = startValue.longitude - endValue.longitude;! if (Math.abs(lngDelta) >= 180) {! endValueLng = endValue.longitude + Math.signum(lngDelta) * 360;! }! ! double newLat = eval(fraction, startValue.latitude, endValue.latitude);! double newLng = eval(fraction, startValue.longitude, endValueLng);! return new LatLng(newLat, newLng);! }! ! private static double eval(float fraction, double startValue, double endValue) {! return startValue + fraction * (endValue - startValue);! }! }

1

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static final LatLngEvaluator LAT_LNG_EVALUATOR = new LatLngEvaluator();! private static final Property MARKER_POSITION_PROPERTY =! Property.of(Marker.class, LatLng.class, "position");! ! private static final LatLng LYON = new LatLng(45.764043, 4.835659);! private static final LatLng PARIS = new LatLng(48.856614, 2.3522219);! ! private LatLng mCurrentDest = LYON;! private Marker mMarker;! private ObjectAnimator mMarkerAnimator;! ! private void setUpMap() {! mMap.setOnMarkerClickListener(mOnMarkerClickListener);! mMarker = mMap.addMarker(new MarkerOptions().position(mCurrentDest));! }! ! ! ! ! ! ! ! !

19

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ! ! ! ! ! ! private final GoogleMap.OnMarkerClickListener mOnMarkerClickListener =! new GoogleMap.OnMarkerClickListener() {! @Override! public boolean onMarkerClick(Marker marker) {! mCurrentDest = mCurrentDest.equals(LYON) ? PARIS : LYON;! if (mMarkerAnimator == null) {! mMarkerAnimator = ObjectAnimator.ofObject(mMarker,! MARKER_POSITION_PROPERTY,! LAT_LNG_EVALUATOR,! mCurrentDest);! } else {! mMarkerAnimator.setObjectValues(mCurrentDest);! }! mMarkerAnimator.start();! return true;! }! };!

config.proguard

config.proguard -keepclassmembers public class! com.google.android.gms.maps.model.Marker {! void setPosition(***);! *** getPosition();! }

Need more?

Need more? googlemaps.github.io/android-maps-utils/

Thank you!

Thank you! @cyrilmottier cyrilmottier.com