Skip to content

Instantly share code, notes, and snippets.

@cosmith
Created October 28, 2015 13:30
Show Gist options
  • Save cosmith/da45976e1005e7f8aa0f to your computer and use it in GitHub Desktop.
Save cosmith/da45976e1005e7f8aa0f to your computer and use it in GitHub Desktop.
MapView Android
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule GoogleMapView
* @flow
*/
"use strict";
var NativeMethodsMixin = require("NativeMethodsMixin");
var React = require("React");
var View = require("View");
var requireNativeComponent = require("requireNativeComponent");
var GoogleMapView = React.createClass({
mixins: [NativeMethodsMixin],
name: "GoogleMapView",
propTypes: {
/**
* Used to style and layout the `GoogleMapView`. See `StyleSheet.js` and
* `ViewStylePropTypes.js` for more info.
*/
style: View.propTypes.style,
/**
* If `true` the app will ask for the user's location.
* Default value is `false`.
*
* **NOTE**: You need to add NSLocationWhenInUseUsageDescription key in
* Info.plist to enable geolocation, otherwise it is going
* to *fail silently*!
*/
myLocationEnabled: React.PropTypes.bool,
/**
* If `true` will show traffic information on the map.
* Default `value` is false.
*/
trafficEnabled: React.PropTypes.bool,
/**
* If `true` will show map information inside buildings
* Default `value` is true
*/
indoorEnabled: React.PropTypes.bool,
mapType: React.PropTypes.oneOf([1, 2, 3, 4, 5]),
cameraPosition: React.PropTypes.shape({
zoom: React.PropTypes.number,
latitude: React.PropTypes.number,
longitude: React.PropTypes.number,
bearing: React.PropTypes.number,
viewingAngle: React.PropTypes.number,
duration: React.PropTypes.number,
}),
/**
* Markers array
*/
markers: React.PropTypes.arrayOf(React.PropTypes.shape({
key: React.PropTypes.number.isRequired,
position: React.PropTypes.shape({
latitude: React.PropTypes.number.isRequired,
longitude: React.PropTypes.number.isRequired,
}).isRequired,
image: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
snippet: React.PropTypes.string,
})),
/**
* Callback that is called continuously when the user is dragging the map.
*/
onCameraChange: React.PropTypes.func,
/**
* Callback that is called once, when the user is done moving the map.
*/
onCameraChangeComplete: React.PropTypes.func,
/**
* Callback that is called when the user taps a marker.
* `param` key the marker key
*/
onMarkerTap: React.PropTypes.func,
/**
* Callback that is called when the user taps the info window of a marker.
* `param` key the marker key
*/
onMarkerInfoWindowTap: React.PropTypes.func,
/**
* Callback that is called when the user taps on the map.
* `param` position: {latitude, longitude} the tapped position
*/
onMapTap: React.PropTypes.func,
size: React.PropTypes.number,
scaleX: React.PropTypes.number,
scaleY: React.PropTypes.number,
translateX: React.PropTypes.number,
translateY: React.PropTypes.number,
rotation: React.PropTypes.number,
},
_onChange: function (event) {
if (event.nativeEvent.continuous) {
this.props.onCameraChange &&
this.props.onCameraChange(event.nativeEvent.region);
} else {
this.props.onCameraChangeComplete &&
this.props.onCameraChangeComplete(event.nativeEvent.region);
}
},
_onMapTap: function (event) {
this.props.onMapTap &&
this.props.onMapTap(event.nativeEvent.position);
},
_onMarkerTap: function (event) {
this.props.onMarkerTap &&
this.props.onMarkerTap(event.nativeEvent.markerKey);
},
_onMarkerInfoWindowTap: function (event) {
this.props.onMarkerInfoWindowTap &&
this.props.onMarkerInfoWindowTap(event.nativeEvent.markerKey);
},
render: function () {
return <RCTGoogleMap {...this.props}
onChange={this._onChange}
onMapTap={this._onMapTap}
onMarkerTap={this._onMarkerTap}
onMarkerInfoWindowTap={this._onMarkerInfoWindowTap}
/>;
},
});
var RCTGoogleMap = requireNativeComponent("RCTGoogleMap", GoogleMapView);
module.exports = GoogleMapView;
package com.truckfly.truckfly;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.google.android.gms.maps.GoogleMap;
import java.util.HashMap;
import java.util.Map;
public class GoogleMapModule extends ReactContextBaseJavaModule {
public GoogleMapModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "GoogleMapManager";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("mapTypeNone", GoogleMap.MAP_TYPE_NONE);
constants.put("mapTypeHybrid", GoogleMap.MAP_TYPE_HYBRID);
constants.put("mapTypeSatellite", GoogleMap.MAP_TYPE_SATELLITE);
constants.put("mapTypeNormal", GoogleMap.MAP_TYPE_NORMAL);
constants.put("mapTypeTerrain", GoogleMap.MAP_TYPE_TERRAIN);
return constants;
}
}
package com.truckfly.truckfly;
import android.app.Activity;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GoogleMapPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new GoogleMapModule(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new GoogleMapViewManager());
}
}
package com.truckfly.truckfly;
import android.content.Context;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapView;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import java.util.ArrayList;
import java.util.HashMap;
public class GoogleMapView extends MapView implements
OnMapReadyCallback,
GoogleMap.OnMarkerClickListener,
GoogleMap.OnInfoWindowClickListener,
GoogleMap.OnCameraChangeListener,
GoogleMap.OnMapClickListener {
private GoogleMap mMap;
private static int CAMERA_CHANGE_REACT_THRESHOLD_MS = 500;
private long lastCameraChangeCall = Long.MIN_VALUE;
private HashMap<Double, Marker> mMarkerCache;
private HashMap<Marker, Double> mMarkerKeys;
public GoogleMapView(Context context) {
super(context);
onCreate(null);
onResume();
getMapAsync(this);
mMarkerCache = new HashMap<>();
mMarkerKeys = new HashMap<>();
}
public void setCameraPosition(ReadableMap options) {
if (mMap == null) {
Log.w("GoogleMapView", "map not initialized");
return;
}
CameraPosition.Builder builder = CameraPosition.builder(mMap.getCameraPosition());
if (options.hasKey("latitude") || options.hasKey("longitude")) {
builder.target(getLatLng(options));
}
if (options.hasKey("zoom")) {
builder.zoom((float) options.getDouble("zoom"));
}
if (options.hasKey("viewingAngle")) {
builder.tilt((float) options.getDouble("viewingAngle"));
}
if (options.hasKey("bearing")) {
builder.bearing((float)options.getDouble("bearing")).build();
}
CameraUpdate update = CameraUpdateFactory.newCameraPosition(builder.build());
if (options.hasKey("duration")) {
int duration = (int)Math.round(options.getDouble("duration") * 1000);
mMap.animateCamera(update, duration, null);
} else {
mMap.animateCamera(update);
}
}
public void setMarkers(ReadableArray markers) {
if (mMap == null) {
return;
}
HashMap<Double, Boolean> currentMarkersKeys = new HashMap<>();
ArrayList<Double> toRemove = new ArrayList<>();
for (int i = 0; i < markers.size(); i++) {
ReadableMap markerData = markers.getMap(i);
Double key = markerData.getDouble("key");
Marker marker = mMarkerCache.get(key);
currentMarkersKeys.put(key, true);
if (marker == null) {
marker = mMap.addMarker(createMarkerOptions(markerData));
mMarkerCache.put(key, marker);
mMarkerKeys.put(marker, key);
}
else {
updateMarker(marker, markerData);
}
}
for (Double key : mMarkerCache.keySet()) {
if (!currentMarkersKeys.containsKey(key)) {
toRemove.add(key);
}
}
for (Double key : toRemove) {
Marker marker = mMarkerCache.get(key);
mMarkerKeys.remove(marker);
marker.remove();
mMarkerCache.remove(key);
}
}
private MarkerOptions createMarkerOptions(ReadableMap markerData) {
MarkerOptions markerOptions = new MarkerOptions()
.position(getLatLng(markerData.getMap("position")))
.icon(getIconFromName(markerData.getString("image")));
if (markerData.hasKey("title")) {
markerOptions.title(markerData.getString("title"));
}
if (markerData.hasKey("snippet")) {
markerOptions.snippet(markerData.getString("snippet"));
}
return markerOptions;
}
private void updateMarker(Marker marker, ReadableMap markerData) {
marker.setPosition(getLatLng(markerData.getMap("position")));
if (markerData.hasKey("image")) {
marker.setIcon(getIconFromName(markerData.getString("image")));
}
if (markerData.hasKey("title")) {
marker.setTitle(markerData.getString("title"));
}
if (markerData.hasKey("snippet")) {
marker.setSnippet(markerData.getString("snippet"));
}
}
private BitmapDescriptor getIconFromName(String name) {
int drawable = getResources().getIdentifier(
name, "drawable", getContext().getPackageName()
);
return BitmapDescriptorFactory.fromResource(drawable);
}
@Override
public void onMapReady(GoogleMap map) {
mMap = map;
mMap.getMyLocation();
mMap.getUiSettings().setCompassEnabled(false);
mMap.getUiSettings().setIndoorLevelPickerEnabled(false);
mMap.getUiSettings().setMyLocationButtonEnabled(false);
mMap.getUiSettings().setTiltGesturesEnabled(false);
mMap.getUiSettings().setZoomControlsEnabled(false);
mMap.setOnCameraChangeListener(this);
mMap.setOnMapClickListener(this);
mMap.setOnMarkerClickListener(this);
mMap.setOnInfoWindowClickListener(this);
}
@Override
public void onMapClick(LatLng position) {
WritableMap event = Arguments.createMap();
event.putMap("position", getLatLng(position));
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topMapTap", event);
}
@Override
public void onCameraChange(CameraPosition cameraPosition) {
final long snap = System.currentTimeMillis();
if (lastCameraChangeCall + CAMERA_CHANGE_REACT_THRESHOLD_MS > snap) {
sendCameraChangeEvent(cameraPosition, true);
lastCameraChangeCall = snap;
return;
}
sendCameraChangeEvent(cameraPosition, false);
lastCameraChangeCall = snap;
}
private void sendCameraChangeEvent(CameraPosition cameraPosition, boolean continuous) {
WritableMap event = Arguments.createMap();
WritableMap region = Arguments.createMap();
WritableMap boundsMap = Arguments.createMap();
LatLngBounds bounds = mMap.getProjection().getVisibleRegion().latLngBounds;
boundsMap.putMap("southWest", getLatLng(bounds.southwest));
boundsMap.putMap("northEast", getLatLng(bounds.northeast));
region.putDouble("zoom", cameraPosition.zoom);
region.putDouble("bearing", cameraPosition.bearing);
region.putDouble("viewingAngle", cameraPosition.tilt);
region.putMap("position", getLatLng(cameraPosition.target));
region.putMap("bounds", boundsMap);
event.putMap("region", region);
event.putBoolean("continuous", continuous);
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topChange", event);
}
@Override
public boolean onMarkerClick(Marker marker) {
WritableMap event = Arguments.createMap();
event.putDouble("markerKey", mMarkerKeys.get(marker));
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topMarkerTap", event);
return false;
}
@Override
public void onInfoWindowClick(Marker marker) {
WritableMap event = Arguments.createMap();
event.putDouble("markerKey", mMarkerKeys.get(marker));
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topMarkerInfoWindowTap", event);
}
private LatLng getLatLng(ReadableMap map) {
if (!map.hasKey("latitude") || !map.hasKey("longitude")) {
return null;
}
return new LatLng(map.getDouble("latitude"), map.getDouble("longitude"));
}
private WritableMap getLatLng(LatLng position) {
WritableMap map = Arguments.createMap();
map.putDouble("latitude", position.latitude);
map.putDouble("longitude", position.longitude);
return map;
}
}
package com.truckfly.truckfly;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import java.util.Map;
public class GoogleMapViewManager extends SimpleViewManager<GoogleMapView> {
public static final String REACT_CLASS = "RCTGoogleMap";
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("topMarkerTap",
MapBuilder.of("phasedRegistrationNames",
MapBuilder.of(
"bubbled", "onMarkerTap",
"captured", "onMarkerTapCaptured")))
.put("topMarkerInfoWindowTap",
MapBuilder.of("phasedRegistrationNames",
MapBuilder.of(
"bubbled", "onMarkerInfoWindowTap",
"captured", "onMarkerInfoWindowTapCaptured")))
.build();
}
@ReactProp(name = "myLocationEnabled")
public void setMyLocationEnabled(GoogleMapView view, boolean enabled) {
view.getMap().setMyLocationEnabled(enabled);
}
@ReactProp(name = "trafficEnabled")
public void setTrafficEnabled(GoogleMapView view, boolean enabled) {
view.getMap().setTrafficEnabled(enabled);
}
@ReactProp(name = "indoorEnabled")
public void setIndoorEnabled(GoogleMapView view, boolean enabled) {
view.getMap().setIndoorEnabled(enabled);
}
@ReactProp(name = "mapType")
public void setMapType(GoogleMapView view, int type) {
view.getMap().setMapType(type);
}
@ReactProp(name = "cameraPosition")
public void setCameraPosition(GoogleMapView view, ReadableMap options) {
view.setCameraPosition(options);
}
@ReactProp(name = "markers")
public void setMarkers(GoogleMapView view, ReadableArray markers) {
view.setMarkers(markers);
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public GoogleMapView createViewInstance(ThemedReactContext context) {
return new GoogleMapView(context);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment