Created
October 28, 2015 13:30
-
-
Save cosmith/da45976e1005e7f8aa0f to your computer and use it in GitHub Desktop.
MapView Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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