My Location button with GoogleMaps

Introduction

Recently, the behaviour of the My location button in the official Google Maps application has been changed. Before that, the My location button only moved the camera to the users location. Now, pressing the new Floating Action Button, the camera not only moves to the users actual location, but gets locked to it. This effect lasts until the user click the button again, or drags the map.

Unfortunately, the Google Maps API has no onMapDragged() event callback, and implementing the official My location button behaviour in your app is not as easy as it may seems at first.

my location button

Initialization

The Floating Action Button widget is available in the new Design Support Library. We use this as our My Location button. To handle map state changes, we utilize Map State Listener by Mads Frandsen.

Initialize our map in the following way...

@Override
public void onMapReady(GoogleMap googleMap) {  
    googleMap.setMyLocationEnabled(true);
    googleMap.getUiSettings().setMyLocationButtonEnabled(false);
    googleMap.setOnMyLocationChangeListener(this);
    createMapStateListener();
}

... and handle location and map state change.

@Override
public void onMyLocationChange(Location location) {  
    myPosition = new LatLng(location.getLatitude(), location.getLongitude());
    if (mapMode == MapMode.FOLLOW_USER  && !mapTouched) {
        goToMyLatLng(myPosition);
    }
}

private void createMapStateListener() {  
    mapStateListener = new MapStateListener(getMapFragment().getMap(), getMapFragment(), this) {
        @Override
        public void onMapTouched() {
            mapTouched = true;
        }

        @Override
        public void onMapReleased() {
            if (mapMode == MapMode.FOLLOW_USER) {
                switchMapMode();
            }
            mapTouched = false;
        }

        @Override
        public void onMapUnsettled() {
        }

        @Override
        public void onMapSettled() {
            if (getMapFragment().getMap().getCameraPosition().target == myPosition) {
                zoomLevel = (int) getMapFragment().getMap().getCameraPosition().zoom;
            }
        }
    };
}
State

The state of the my location button is indicated with boolean isFreeDrag, boolean mapTouched and LatLng myPosition. When the user press the My location button we switch state ...

private void switchMapMode() {  
    isFreeDrag=!isFreeDrag;
    switchFabIcon();
}

private void switchFabIcon() {  
    if (isFreeDrag) {
        floatingButton.setImageResource(R.drawable.ic_my_location_free_drag);
    } else {
        floatingButton.setImageResource(R.drawable.ic_my_location_locked);
    }
}

... and move the camera to the user's location.

private void goToMyLatLng(LatLng latLng) {  
    getMapFragment().getMap()
        .moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoomLevel));
}
Configuration change

As this state is not handled by the MapsFragment, we need to take care about persisting it upon configuration change ie. screen rotation, with the current zoom level.

@Override
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        isFreeDrag =  savedInstanceState.getBoolean(KEY_IS_FREE_DRAG);
        zoomLevel = savedInstanceState.getInt(KEY_ZOOMLEVEL);
        myPosition = (LatLng) savedInstanceState.getParcelable(KEY_MYPOSITION);
    }
 //...
}

//...
@Override
protected void onSaveInstanceState(Bundle outState) {  
   outState.putBoolean(KEY_IS_FREE_DRAG), isFreeDrag);
   outState.putInt(KEY_ZOOMLEVEL, zoomLevel);
   outState.putParcelable(KEY_MYPOSITION, myPosition);
   super.onSaveInstanceState(outState);
}