From ae3e06e83d7ecadc0fb211af41ca01c94c544481 Mon Sep 17 00:00:00 2001 From: Guilherme Eduardo Gulinoski Teixeira Date: Tue, 26 Jun 2018 06:42:08 -0300 Subject: [PATCH] Added animateToNavigation method to MapView (#2049) * added animateToNavigation method to MapView * Added AnimatedNavigation example * Updated Fork * Deleted IDE files and Unnecessary .class files --- docs/mapview.md | 1 + example/App.js | 2 + example/android/app/.project | 23 +++ .../maps/example/ExampleApplication.class | Bin 0 -> 2567 bytes .../react/maps/example/MainActivity.class | Bin 0 -> 1152 bytes example/examples/AnimatedNavigation.js | 137 ++++++++++++++++++ example/examples/assets/car.png | Bin 0 -> 2825 bytes index.d.ts | 1 + lib/android/.project | 23 +++ .../main/java/com/airbnb/android/react/maps | 0 .../android/react/maps/AirMapManager.java | 32 +++- .../airbnb/android/react/maps/AirMapView.java | 10 ++ lib/components/MapView.js | 4 + lib/ios/AirGoogleMaps/AIRGoogleMapManager.m | 23 +++ lib/ios/AirMaps/AIRMapManager.m | 24 +++ 15 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 example/android/app/.project create mode 100644 example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/ExampleApplication.class create mode 100644 example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/MainActivity.class create mode 100644 example/examples/AnimatedNavigation.js create mode 100644 example/examples/assets/car.png create mode 100644 lib/android/.project create mode 100644 lib/android/bin/src/main/java/com/airbnb/android/react/maps diff --git a/docs/mapview.md b/docs/mapview.md index dab74f93e..b4656c526 100644 --- a/docs/mapview.md +++ b/docs/mapview.md @@ -68,6 +68,7 @@ To access event data, you will need to use `e.nativeEvent`. For example, `onPres | Method Name | Arguments | Notes |---|---|---| +| `animateToNavigation` | `location: LatLng`, `bearing: Number`, `angle: Number`, `duration: Number` | | `animateToRegion` | `region: Region`, `duration: Number` | | `animateToCoordinate` | `coordinate: LatLng`, `duration: Number` | | `animateToBearing` | `bearing: Number`, `duration: Number` | diff --git a/example/App.js b/example/App.js index ebd4d8c80..7b6231e61 100644 --- a/example/App.js +++ b/example/App.js @@ -40,6 +40,7 @@ import MapKml from './examples/MapKml'; import BugMarkerWontUpdate from './examples/BugMarkerWontUpdate'; import ImageOverlayWithAssets from './examples/ImageOverlayWithAssets'; import ImageOverlayWithURL from './examples/ImageOverlayWithURL'; +import AnimatedNavigation from './examples/AnimatedNavigation'; import OnPoiClick from './examples/OnPoiClick'; const IOS = Platform.OS === 'ios'; @@ -159,6 +160,7 @@ class App extends React.Component { [BugMarkerWontUpdate, 'BUG: Marker Won\'t Update (Android)', true], [ImageOverlayWithAssets, 'Image Overlay Component with Assets', true], [ImageOverlayWithURL, 'Image Overlay Component with URL', true], + [AnimatedNavigation, 'Animated Map Navigation', true], [OnPoiClick, 'On Poi Click', true], ] // Filter out examples that are not yet supported for Google Maps on iOS. diff --git a/example/android/app/.project b/example/android/app/.project new file mode 100644 index 000000000..ca3856c62 --- /dev/null +++ b/example/android/app/.project @@ -0,0 +1,23 @@ + + + example-android + Project example-android created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/ExampleApplication.class b/example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/ExampleApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..3ed43fa991ade2004952ac83f41a36fac1799b7e GIT binary patch literal 2567 zcmds3T~FIE6ussvZRp0=Hny=3!!}l4nwLF|X%a&qkOEB5A??vgOlwH&$Z;$97yA?Y z4U;x$d*6?mc9XVhDFMR7(;gbf?(xaF*T>iX{pb5n0C)pc3q}ZRCfau*CZu!B+RX7) z>3zlxt(7jp9Wow$lA0$f zZ1aGk-dKd01R87Qg*-~$QoCGlavE?yWEw#+^G{fV(g-(bY12wKTd~H@YPn zD4tNM42_uao75l|KaDY-K`zW2P7|`0N~{%Q?&uOxthAv$1uTxK&t&>NkJDac=b(M| zBFH1=b5E(0{|%cZx@AOoU0_!O2V9#&M2(`1nzNYO<;~cZU1EYn!oUfCsf?Tdh%%#c(vi`BVWiM`w!3A2=nw=#% ze0F&Uw`R6}#h7-m02D(O40Nc-YR^)MAPpw5@)>S?W54%?D4vv|oNC<4Xw{zy;!7 zXUxKWOBxaJZP^xyK#^-{$<0m<)MUH8q^_3WF@diI&Y0eeoUHx(+73l?H8+8hlIxl? z!%Og#z?a)oeLLpC#2%=Y_W7nrT`_<0;*cGm=&Wt&E16(*;L>afu9^0mXBI3Gm>iH| z!ArbmWH~xFx{Mwi${c~pjz|)$Ty-qw2`mC$FYw}nrh##oz_(F+x1fa2a&{p~`vCS2 zus_dMzQNsJ*c*Wvd`@Klq%#339Td#LJ?x=!cz~$+F6tq+7ug==ur21YEufz3*q-FD bE$6U}!ZTDe0?&bB3-B6$C5(CzUcjrrpsXkm literal 0 HcmV?d00001 diff --git a/example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/MainActivity.class b/example/android/app/bin/src/main/java/com/airbnb/android/react/maps/example/MainActivity.class new file mode 100644 index 0000000000000000000000000000000000000000..41f9343a16ddd63938a5e1b8c20d858cada4dd1e GIT binary patch literal 1152 zcmbtTT~8B16ur|H+AhUHEq>w0z*BkIeeuZ}BNB}y1rn$vzL?C;UfY4)na%99fnR2# ziN5=zjCZ$ffY4wQALcXXo;h>wy}$o_{|NvuVXF!y25&PfgG4ADoQbgrq)7syY^cLP z=*Sus1r~*L1WB_1FGP~Y7#t9OS2{IT?y?FM25UudEc75a3eQkF2FtINR_+aha%bn1 zL1oWGScN)+<`wq6wZ_(9g|RQ6w8hNCV~jXyNL4JHGMcB>gfS-BEAG|%L*x<4SXct3 zLY|8O@@+!<6UF{iv0o_mbH)Bt@ivbPW?UP`6XE2LJL1BNl$2iQA}=URX+M|fFUCC4 zrIOTGmrM7*3W=dH=UfV%KNupPea)*M=Vs2Vn}guaaKYVWieCL_jMl0M{{cI3R_Vdr zAP;J=+JGgv$6%w=yUM^MxSMCF*;5)1M@fjbFUWKTt)7u0J{4Bw=jmm|4OPbAUGIMj zr^}!{K$q9j9%bBU)b3Cu^lrPeb6X;V&Et`FD#3j{R+%D2yScwbosb}}H}k7ogGUVX z0u?ojxZAw?A`X*)6Em_BKd9Wg_3JHCloJ`hucgtM${hXlqe3gJ98&*0W3YOoE~@b2 zMoUfJXV5rMSw^|;Q+Z^t1=L&A$p9tVEx|I))uOY@v;**dK<#tO`v#3)1*S=JjsY5L xG&d$FScev!DsUg##I!L*J)reA+rv3*D|6V&@R;ID@B}t#1>iK6>HHL){{=LjOq&1z literal 0 HcmV?d00001 diff --git a/example/examples/AnimatedNavigation.js b/example/examples/AnimatedNavigation.js new file mode 100644 index 000000000..ce7b85f99 --- /dev/null +++ b/example/examples/AnimatedNavigation.js @@ -0,0 +1,137 @@ +import React, { Component } from 'react'; + +import { + View, + StyleSheet, + TouchableOpacity, + Text, +} from 'react-native'; + +import MapView from 'react-native-maps'; +import carImage from './assets/car.png'; + +export default class NavigationMap extends Component { + + constructor(props) { + super(props); + this.state = { + prevPos: null, + curPos: { latitude: 37.420814, longitude: -122.081949 }, + curAng: 45, + latitudeDelta: 0.0922, + longitudeDelta: 0.0421, + }; + this.changePosition = this.changePosition.bind(this); + this.getRotation = this.getRotation.bind(this); + this.updateMap = this.updateMap.bind(this); + } + + changePosition(latOffset, lonOffset) { + const latitude = this.state.curPos.latitude + latOffset; + const longitude = this.state.curPos.longitude + lonOffset; + this.setState({ prevPos: this.state.curPos, curPos: { latitude, longitude } }); + this.updateMap(); + } + + getRotation(prevPos, curPos) { + if (!prevPos) return 0; + const xDiff = curPos.latitude - prevPos.latitude; + const yDiff = curPos.longitude - prevPos.longitude; + return (Math.atan2(yDiff, xDiff) * 180.0) / Math.PI; + } + + updateMap() { + const { curPos, prevPos, curAng } = this.state; + const curRot = this.getRotation(prevPos, curPos); + this.map.animateToNavigation(curPos, curRot, curAng); + } + + render() { + return ( + + (this.map = el)} + style={styles.flex} + minZoomLevel={15} + initialRegion={{ + ...this.state.curPos, + latitudeDelta: this.state.latitudeDelta, + longitudeDelta: this.state.longitudeDelta, + }} + > + + + + this.changePosition(0.0001, 0)} + > + + Lat + + this.changePosition(-0.0001, 0)} + > + - Lat + + + + this.changePosition(0, -0.0001)} + > + - Lon + + this.changePosition(0, 0.0001)} + > + + Lon + + + + ); + } +} + +const styles = StyleSheet.create({ + flex: { + flex: 1, + width: '100%', + }, + buttonContainerUpDown: { + ...StyleSheet.absoluteFillObject, + flexDirection: 'row', + justifyContent: 'center', + }, + buttonContainerLeftRight: { + ...StyleSheet.absoluteFillObject, + flexDirection: 'column', + justifyContent: 'center', + }, + button: { + backgroundColor: 'rgba(100,100,100,0.2)', + position: 'absolute', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 20, + height: 50, + width: 50, + }, + up: { + alignSelf: 'flex-start', + }, + down: { + alignSelf: 'flex-end', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, +}); diff --git a/example/examples/assets/car.png b/example/examples/assets/car.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe9aa16324ac187e48742ee0787913361421cea GIT binary patch literal 2825 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 Z0N{8eXBHWc8H3+g0Bde%#>8BU{6A&!KLY>& literal 0 HcmV?d00001 diff --git a/index.d.ts b/index.d.ts index 6b15f0f45..673e66d6e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -98,6 +98,7 @@ declare module "react-native-maps" { } export default class MapView extends React.Component { + animateToNavigation(location: LatLng, bearing: number, angle: number, duration?: number): void; animateToRegion(region: Region, duration?: number): void; animateToCoordinate(latLng: LatLng, duration?: number): void; animateToBearing(bearing: number, duration?: number): void; diff --git a/lib/android/.project b/lib/android/.project new file mode 100644 index 000000000..2a84911fc --- /dev/null +++ b/lib/android/.project @@ -0,0 +1,23 @@ + + + react-native-maps-lib + Project react-native-maps-lib created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/lib/android/bin/src/main/java/com/airbnb/android/react/maps b/lib/android/bin/src/main/java/com/airbnb/android/react/maps new file mode 100644 index 000000000..e69de29bb diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java index 43651f7e2..ee80725c1 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapManager.java @@ -22,6 +22,7 @@ import com.google.maps.android.data.kml.KmlLayer; import java.util.Map; +import java.util.HashMap; import javax.annotation.Nullable; @@ -36,6 +37,7 @@ public class AirMapManager extends ViewGroupManager { private static final int FIT_TO_SUPPLIED_MARKERS = 6; private static final int FIT_TO_COORDINATES = 7; private static final int SET_MAP_BOUNDARIES = 8; + private static final int ANIMATE_TO_NAVIGATION = 9; private final Map MAP_TYPES = MapBuilder.of( @@ -251,6 +253,17 @@ public void receiveCommand(AirMapView view, int commandId, @Nullable ReadableArr ReadableMap region; switch (commandId) { + case ANIMATE_TO_NAVIGATION: + region = args.getMap(0); + lng = region.getDouble("longitude"); + lat = region.getDouble("latitude"); + LatLng location = new LatLng(lat, lng); + bearing = (float)args.getDouble(1); + angle = (float)args.getDouble(2); + duration = args.getInt(3); + view.animateToNavigation(location, bearing, angle, duration); + break; + case ANIMATE_TO_REGION: region = args.getMap(0); duration = args.getInt(1); @@ -332,14 +345,15 @@ public Map getExportedCustomDirectEventTypeConstants() { @Nullable @Override public Map getCommandsMap() { - Map map = MapBuilder.of( + Map map = this.CreateMap( "animateToRegion", ANIMATE_TO_REGION, "animateToCoordinate", ANIMATE_TO_COORDINATE, "animateToViewingAngle", ANIMATE_TO_VIEWING_ANGLE, "animateToBearing", ANIMATE_TO_BEARING, "fitToElements", FIT_TO_ELEMENTS, "fitToSuppliedMarkers", FIT_TO_SUPPLIED_MARKERS, - "fitToCoordinates", FIT_TO_COORDINATES + "fitToCoordinates", FIT_TO_COORDINATES, + "animateToNavigation", ANIMATE_TO_NAVIGATION ); map.putAll(MapBuilder.of( @@ -349,6 +363,20 @@ public Map getCommandsMap() { return map; } + public static Map CreateMap( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) { + Map map = new HashMap(); + map.put(k1, v1); + map.put(k2, v2); + map.put(k3, v3); + map.put(k4, v4); + map.put(k5, v5); + map.put(k6, v6); + map.put(k7, v7); + map.put(k8, v8); + return map; + } + @Override public LayoutShadowNode createShadowNodeInstance() { // A custom shadow node is needed in order to pass back the width/height of the map to the diff --git a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java index f024ad923..60a27609b 100644 --- a/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java +++ b/lib/android/src/main/java/com/airbnb/android/react/maps/AirMapView.java @@ -609,6 +609,16 @@ public void updateExtraData(Object extraData) { } } + public void animateToNavigation(LatLng location, float bearing, float angle, int duration) { + if (map == null) return; + CameraPosition cameraPosition = new CameraPosition.Builder(map.getCameraPosition()) + .bearing(bearing) + .tilt(angle) + .target(location) + .build(); + map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), duration, null); + } + public void animateToRegion(LatLngBounds bounds, int duration) { if (map == null) return; map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0), duration, null); diff --git a/lib/components/MapView.js b/lib/components/MapView.js index 03f50bf0d..a72b5a391 100644 --- a/lib/components/MapView.js +++ b/lib/components/MapView.js @@ -538,6 +538,10 @@ class MapView extends React.Component { } } + animateToNavigation(location, bearing, angle, duration) { + this._runCommand('animateToNavigation', [location, bearing, angle, duration || 500]); + } + animateToRegion(region, duration) { this._runCommand('animateToRegion', [region, duration || 500]); } diff --git a/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m b/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m index d22155814..b768bf523 100644 --- a/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m +++ b/lib/ios/AirGoogleMaps/AIRGoogleMapManager.m @@ -78,6 +78,29 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(maxZoomLevel, CGFloat) RCT_EXPORT_VIEW_PROPERTY(kmlSrc, NSString) +RCT_EXPORT_METHOD(animateToNavigation:(nonnull NSNumber *)reactTag + withRegion:(MKCoordinateRegion)region + withBearing:(CGFloat)bearing + withAngle:(double)angle + withDuration:(CGFloat)duration) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + id view = viewRegistry[reactTag]; + if (![view isKindOfClass:[AIRGoogleMap class]]) { + RCTLogError(@"Invalid view returned from registry, expecting AIRGoogleMap, got: %@", view); + } else { + [CATransaction begin]; + [CATransaction setAnimationDuration:duration/1000]; + AIRGoogleMap *mapView = (AIRGoogleMap *)view; + GMSCameraPosition *camera = [AIRGoogleMap makeGMSCameraPositionFromMap:mapView andMKCoordinateRegion:region]; + [mapView animateToCameraPosition:camera]; + [mapView animateToViewingAngle:angle]; + [mapView animateToBearing:bearing]; + [CATransaction commit]; + } + }]; +} + RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag withRegion:(MKCoordinateRegion)region withDuration:(CGFloat)duration) diff --git a/lib/ios/AirMaps/AIRMapManager.m b/lib/ios/AirMaps/AIRMapManager.m index e0e112b5a..47f38ac7f 100644 --- a/lib/ios/AirMaps/AIRMapManager.m +++ b/lib/ios/AirMaps/AIRMapManager.m @@ -129,6 +129,30 @@ - (UIView *)view #pragma mark exported MapView methods +RCT_EXPORT_METHOD(animateToNavigation:(nonnull NSNumber *)reactTag + withRegion:(MKCoordinateRegion)region + withBearing:(CGFloat)bearing + withAngle:(double)angle + withDuration:(CGFloat)duration) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + id view = viewRegistry[reactTag]; + if (![view isKindOfClass:[AIRMap class]]) { + RCTLogError(@"Invalid view returned from registry, expecting AIRMap, got: %@", view); + } else { + AIRMap *mapView = (AIRMap *)view; + MKMapCamera *mapCamera = [[mapView camera] copy]; + [mapCamera setPitch:angle]; + [mapCamera setHeading:bearing]; + + [AIRMap animateWithDuration:duration/1000 animations:^{ + [(AIRMap *)view setRegion:region animated:YES]; + [mapView setCamera:mapCamera animated:YES]; + }]; + } + }]; +} + RCT_EXPORT_METHOD(animateToRegion:(nonnull NSNumber *)reactTag withRegion:(MKCoordinateRegion)region withDuration:(CGFloat)duration)