ネイティブマップを反応:どのようにインストールし、IOSとAndroid


マップは、我々の携帯電話にインストールされているアプリケーションの多くの最も人気のあるインターフェイスになっている.学習は、地図上で動作するように適切に情報を表し、良いナビゲーションインターフェイスを作成するますます重要になっている.
このポストではどのように反応するネイティブアプリにIOSとAndroid用の反応ネイティブマップライブラリを使用してGoogleマップを統合する方法を参照してください.可能な限り現実的な例を開発するには、ボトムシートを使用してUFOスタイルインターフェイスを再作成します.
ポストの終わりに、我々はここのこのようなアプリケーションを開発することができます.

プロジェクト創造


このプロジェクトのために、我々はインストールプロセスをスピードアップして、アプリケーションをテストするために倉庫をダウンロードしたい誰でも簡単にするためにEXPOを使用するつもりです.万博がまだないならば、あなたは以下に続くことができますofficial installation guide .
我々が行う最初のことは、EXPO CLIを使用して空白のプロジェクトを作成することです.
#We create a project named google-maps-example. We select the "blank" template
$ expo init google-maps-example

$ cd google-maps-example

$ expo start

Googleマップでネイティブマップを作成


プロジェクトが作成されると、次の手順は、以下のコマンドを使用して反応ネイティブマップライブラリを追加することです.
expo install react-native-maps
プロジェクトでExpoを使用していない場合は、このコマンドを使用できます
npm install react-native-maps --save-exact

o

yarn add react-native-maps -E
最初のコマンドと2番目のコマンドの違いは、万博CLIを使用して、我々は万博と互換性のあるライブラリの最新バージョンを使用するようにしてください.
これは、我々は両方のAppleマップとGoogleマップを使用して反応ネイティブマップライブラリを使用することができます言及する価値がある.このチュートリアルでは、Googleマップをマッププロバイダとして使用することに焦点を当てますが、アップルマップを統合する手順は非常に似ています.

グーグルマップAPIキー


GoogleマップをGoogleアプリケーションで使用するには、Googleクラウドコンソールでアクティブな課金アカウントを使用してGoogleプロジェクトでIOSとAndroid SDKを有効にし、APIキーを生成し、CodeBaseに追加する必要があります.
Google Maps APIキーを取得する方法を追って説明しましょう.
  • まず最初に、Googleクラウドコンソールに行って、Google Mapsの例題をリネームする新しいプロジェクトを作成します.
  • 一旦我々が我々のプロジェクトをつくったならば、我々はAPIと地図図書館の中でIOSのためにAndroidと地図SDKのために地図SDKを可能にする必要があります.


  • SDKが有効になったら、APIキーを作成する必要があります.このためにコントロールパネルに行きます→ 資格情報を作成する→ APIキー


  • APIキーが作成されると、使用するライブラリとアプリケーションの指紋と識別子バンドルを使用する許可を持つアプリケーションに制限することを強く推奨します.
  • 今、我々は我々のアプリケーションに追加する必要があるAPIキーを持っている.我々は万博や裸のプロジェクトを使用しているかどうかに応じて、それを行う方法が変更されます.

    エキスポのAPIキーを追加


    エキスポ我々は単にアプリに行く.JSONを追加し、このスニペットを追加します.
    // app.json
    
    {
      "expo": {
        "name": "google-maps-example",
        "slug": "google-maps-example",
        "version": "1.0.0",
        "orientation": "portrait",
        "icon": "./src/assets/icon.png",
        "splash": {
          "image": "./src/assets/splash.png",
          "resizeMode": "contain",
          "backgroundColor": "#ffffff"
        },
        "updates": {
          "fallbackToCacheTimeout": 0
        },
        "assetBundlePatterns": [
          "**/*"
        ],
        "ios": {
          "supportsTablet": true,
          "config": {
              "googleMapsApiKey": "REPLACE_FOR_API_KEY"
          }
        },
        "android": {
          "adaptiveIcon": {
            "foregroundImage": "./src/assets/adaptive-icon.png",
            "backgroundColor": "#FFFFFF"
          },
          "config": {
            "googleMaps": {
              "apiKey": "REPLACE_FOR_API_KEY"
            }
          }
        },
        "web": {
          "favicon": "./src/assets/favicon.png"
        }
      }
    }
    

    AndroidでAPIキーを追加


    それが裸のAndroidプロジェクトであるならば、それはAPIキーを加える必要がありますgoogle_maps_api.xml パスでは、Android/app/src/main/res/valueです.
    <resources>
      <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">(api key here)</string>
    </resources>
    

    IOSのAPIキーを追加


    IOSでは、編集する必要がありますAppDelegate.m 以下の断片を含むファイル.
    + #import <GoogleMaps/GoogleMaps.h>
    @implementation AppDelegate
    ...
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    + [GMSServices provideAPIKey:@"_YOUR_API_KEY_"]; // add this line using the api key obtained from Google Console
    ...
    
      # React Native Maps dependencies
      rn_maps_path = '../node_modules/react-native-maps'
      pod 'react-native-google-maps', :path => rn_maps_path
      pod 'GoogleMaps'
      pod 'Google-Maps-iOS-Utils'
    
    これは重要なことは、場所のアクセス許可を使用するときは、ユーザーの場所にアクセスする必要がAppleを教えてください、それ以外の場合は、アプリケーションストアにアップロードするときにAppleはあなたのアプリケーションを拒否することに注意してください.これはinfoで行うことができます.説明文フィールドを明確に説明するためにNSLOLocationを編集することで、ファイルを表示します.

    を追加し、ネイティブマップ内のマップをカスタマイズする


    私たちはマップライブラリを統合したので、我々はマップの可視化と画面を作成し、それが提供するさまざまなオプションを使用してスタイルをカスタマイズすることによって起動する予定です.このためにマップを作成します.以下のようなJSコンポーネント.
    import React from 'react';
    import { StyleSheet, View, Dimensions } from 'react-native';
    import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
    import { mapStyle } from './mapStyle';
    
    export function MapScreen() {
      return (
        <View style={styles.container}>
          <MapView
            customMapStyle={mapStyle}
            provider={PROVIDER_GOOGLE}
            style={styles.mapStyle}
            initialRegion={{
              latitude: 41.3995345,
              longitude: 2.1909796,
              latitudeDelta: 0.003,
              longitudeDelta: 0.003,
            }}
            mapType="standard"
          ></MapView>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: 'black',
        alignItems: 'center',
        justifyContent: 'center',
      },
      mapStyle: {
        width: Dimensions.get('window').width,
        height: Dimensions.get('window').height,
      },
    });
    
    見ることができるように、メインコンポーネントはMapViewです.この場合、最も重要なものは、Googleマップ、初期位置になるInitialRegionを使用することを示すプロバイダです.ここでは、ロードされたマップの型を定義することができます.最後にCustomMapStyleを使用して、カスタムマップを設定します.

    我々が見るならばGoogle's official documentation 我々は、マップのほとんどすべての要素をカスタマイズすることができます.この場合、ミニマリストインターフェイスを作成します.
    //mapStyle.js
    export const mapStyle = [
      {
        featureType: 'water',
        elementType: 'geometry',
        stylers: [
          {
            color: '#e9e9e9',
          },
          {
            lightness: 17,
          },
        ],
      },
      {
        featureType: 'landscape',
        elementType: 'geometry',
        stylers: [
          {
            color: '#f5f5f5',
          },
          {
            lightness: 20,
          },
        ],
      },
      {
        featureType: 'road.highway',
        elementType: 'geometry.fill',
        stylers: [
          {
            color: '#ffffff',
          },
          {
            lightness: 17,
          },
        ],
      },
      {
        featureType: 'road.highway',
        elementType: 'geometry.stroke',
        stylers: [
          {
            color: '#ffffff',
          },
          {
            lightness: 29,
          },
          {
            weight: 0.2,
          },
        ],
      },
      {
        featureType: 'road.arterial',
        elementType: 'geometry',
        stylers: [
          {
            color: '#ffffff',
          },
          {
            lightness: 18,
          },
        ],
      },
      {
        featureType: 'road.local',
        elementType: 'geometry',
        stylers: [
          {
            color: '#ffffff',
          },
          {
            lightness: 16,
          },
        ],
      },
      {
        featureType: 'poi',
        elementType: 'geometry',
        stylers: [
          {
            color: '#f5f5f5',
          },
          {
            lightness: 21,
          },
        ],
      },
      {
        featureType: 'poi.park',
        elementType: 'geometry',
        stylers: [
          {
            color: '#dedede',
          },
          {
            lightness: 21,
          },
        ],
      },
      {
        elementType: 'labels.text.stroke',
        stylers: [
          {
            visibility: 'on',
          },
          {
            color: '#ffffff',
          },
          {
            lightness: 16,
          },
        ],
      },
      {
        elementType: 'labels.text.fill',
        stylers: [
          {
            saturation: 36,
          },
          {
            color: '#333333',
          },
          {
            lightness: 40,
          },
        ],
      },
      {
        elementType: 'labels.icon',
        stylers: [
          {
            visibility: 'off',
          },
        ],
      },
      {
        featureType: 'transit',
        elementType: 'geometry',
        stylers: [
          {
            color: '#f2f2f2',
          },
          {
            lightness: 19,
          },
        ],
      },
      {
        featureType: 'administrative',
        elementType: 'geometry.fill',
        stylers: [
          {
            color: '#fefefe',
          },
          {
            lightness: 20,
          },
        ],
      },
      {
        featureType: 'administrative',
        elementType: 'geometry.stroke',
        stylers: [
          {
            color: '#fefefe',
          },
          {
            lightness: 17,
          },
          {
            weight: 1.2,
          },
        ],
      },
    ];
    
    Googleマップをカスタマイズすることは面倒なことができますSnazzymaps テンプレートをテンプレートとして直接属性とコピーをコピーすることができます.

    Googleマップにマーカーを追加する


    我々がする次のことは、我々の地図にマーカーを加えることです.これを行うには、次の構造の定数データを作成します.
    import { default as Reboot } from '../assets/reboot.png';
    import { default as Cravy } from '../assets/cravy.png';
    import { default as Dribbble } from '../assets/dribbble.png';
    import { default as Basecamp } from '../assets/basecamp.png';
    import { default as Discord } from '../assets/discord.png';
    import { default as OnePassword } from '../assets/onepassword.png';
    
    export const MARKERS_DATA = [
      {
        id: '1',
        latitude: 41.3997999,
        longitude: 2.1909796,
        color: '#2F3136',
        name: 'Reboot Studio',
        direction: 'Carrer de Pujades, 100',
        img: Reboot,
      },
      {
        id: '2',
        latitude: 41.3995445,
        longitude: 2.1915268,
        color: '#A3EAD8',
        name: 'Cravy',
        direction: 'Carrer de Pujades, 101',
        img: Cravy,
      },
      {
        id: '3',
        latitude: 41.4009999,
        longitude: 2.1919999,
        color: '#E990BB',
        name: 'Dribbble',
        direction: 'Carrer de Pujades, 102',
        img: Dribbble,
      },
      {
        id: '4',
        latitude: 41.4001999,
        longitude: 2.1900096,
        color: '#EFD080',
        name: 'Basecamp',
        direction: 'Carrer de Pujades, 103',
        img: Basecamp,
      },
      {
        id: '5',
        latitude: 41.40009,
        longitude: 2.1909796,
        color: '#98AFE9',
        name: 'Discord',
        direction: 'Carrer de Pujades, 104',
        img: Discord,
      },
      {
        id: '6',
        latitude: 41.4009999,
        longitude: 2.1909796,
        color: '#4E87EB',
        name: '1 Password',
        direction: 'Carrer de Pujades, 105',
        img: OnePassword,
      },
    ];
    
    データを準備したら、MapView内のライブラリのマーカーコンポーネントをインポートすることによってマップに追加できます.これを行うには配列を使用します.我々が作成したMarkersCountデータによるマップ機能.
    //Map.js
    import React from 'react';
    import { StyleSheet, View, Dimensions } from 'react-native';
    import MapView, { PROVIDER_GOOGLE, Marker } from 'react-native-maps';
    import { mapStyle } from './mapStyle';
    import { MARKERS_DATA } from '../../data';
    
    export function MapScreen() {
      return (
        <View style={styles.container}>
          <MapView
            customMapStyle={mapStyle}
            provider={PROVIDER_GOOGLE}
            style={styles.mapStyle}
            initialRegion={{
              latitude: 41.3995345,
              longitude: 2.1909796,
              latitudeDelta: 0.003,
              longitudeDelta: 0.003,
            }}
            mapType="standard"
          >
            {MARKERS_DATA.map((marker) => (
              <Marker
                key={marker.id}
                coordinate={{
                  latitude: marker.latitude,
                  longitude: marker.longitude,
                }}
              ></Marker>
            ))}
          </MapView>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: 'black',
        alignItems: 'center',
        justifyContent: 'center',
      },
      mapStyle: {
        width: Dimensions.get('window').width,
        height: Dimensions.get('window').height,
      },
    });
    
    ベール!我々はすでに地図上にマーカーがあります.しかし、それはまだ標準的なGoogleマップマップのように見えるので、次のステップでは、マーカーのスタイルをカスタマイズすることによっていくつかの人格を与えるつもりです.

    マップ内のカスタマイズGoogleマップマーカー


    反応ネイティブマップライブラリは、マーカーのスタイルをカスタマイズするためにいくつかの小道具が含まれていますが、最適なオプションを完全にカスタマイズされたマーカーを作成する場合は、ラッパーとしてマーカーコンポーネントを使用して、必要なスタイルを使用して独自のコンポーネントを作成することです.
    我々のミニマリストインターフェイスに続いて我々はいくつかの円形マーカーを追加し、我々はスムーズにマーカーが選択されているサイズをアニメーション化されます.
    我々は、アニメーションの相互作用を管理するためにCustomMarkerコンポーネントとusemarkerアニメーションフックを作成するつもりです.
    //Custom Marker
    import React from 'react';
    import { Marker } from 'react-native-maps';
    import Animated from 'react-native-reanimated';
    import { StyleSheet, View } from 'react-native';
    import { useMarkerAnimation } from './useMarkerAnimation';
    
    export function CustomMarker({
      id,
      selectedMarker,
      color,
      latitude,
      longitude,
    }) {
      const scale = useMarkerAnimation({ id, selectedMarker });
    
      return (
        <Marker
          coordinate={{
            latitude: latitude,
            longitude: longitude,
          }}
        >
          <View style={styles.markerWrapper}>
            <Animated.View
              style={[
                styles.marker,
                {
                  backgroundColor: color,
                  transform: [{ scale: scale }],
                },
              ]}
            ></Animated.View>
          </View>
        </Marker>
      );
    }
    
    const styles = StyleSheet.create({
      markerWrapper: {
        height: 50,
        width: 50,
        alignItems: 'center',
        justifyContent: 'center',
      },
      marker: {
        height: 22,
        width: 22,
        borderRadius: 20,
        borderColor: 'white',
        borderWidth: 2,
      },
    });
    
    アニメーションを管理するために我々は、アニメーションとredashライブラリを追加しました.
    //useMarkerAnimation
    import { useState, useEffect } from 'react';
    import Animated from 'react-native-reanimated';
    import { useTimingTransition } from 'react-native-redash';
    
    export function useMarkerAnimation({ id, selectedMarker }) {
      const [active, setActive] = useState(0);
    
      useEffect(() => {
        const isActive = id === selectedMarker ? 1 : 0;
        setActive(isActive);
      }, [id, selectedMarker]);
    
      const transition = useTimingTransition(active, {
        duration: 200,
      });
    
      const scale = Animated.interpolate(transition, {
        inputRange: [0, 1],
        outputRange: [1, 1.5],
      });
    
      return scale;
    }
    
    最後に、我々は我々が作成したカスタムマーカーを使用してマップ画面からデフォルトマーカーを置き換えます.
    //Map.js
    import React from 'react';
    import { StyleSheet, View, Dimensions } from 'react-native';
    import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
    import { CustomMarker } from '../../components';
    import { MARKERS_DATA } from '../../data';
    import { mapStyle } from './mapStyle';
    
    export function MapScreen() {
      return (
        <View style={styles.container}>
          <MapView
            customMapStyle={mapStyle}
            provider={PROVIDER_GOOGLE}
            style={styles.mapStyle}
            initialRegion={{
              latitude: 41.3995345,
              longitude: 2.1909796,
              latitudeDelta: 0.003,
              longitudeDelta: 0.003,
            }}
            mapType="standard"
          >
            {MARKERS_DATA.map((marker) => (
              <CustomMarker
                key={marker.id}
                id={marker.id}
                selectedMarker={null}
                color={marker.color}
                latitude={marker.latitude}
                longitude={marker.longitude}
              ></CustomMarker>
            ))}
          </MapView>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: 'black',
        alignItems: 'center',
        justifyContent: 'center',
      },
      mapStyle: {
        width: Dimensions.get('window').width,
        height: Dimensions.get('window').height,
      },
    });
    
    オーライ!我々はすでに我々の地図アプリでカスタムマーカーがあります.しかし、まだステップが残っている:私たちは別のマーカー間を移動できるようにする必要があります.これを行うには、OverやGoogle Mapsなどのアプリケーションで見つかったものと同様のボトムシートに基づいてインターフェイスを作成します.このコンポーネントは、マーカー間のナビゲーションを管理できます.

    マップナビゲーション


    の両方を使用してマップをナビゲートする方法を見てみましょうanimateCameraanimateToRegion 関数.このためには、これらの関数を使用できるようにマップリファレンスを作成する必要があります.このロジックを管理するフックを作成しました.
    //useMap.js
    import { useState, useRef, useCallback } from 'react';
    
    const DEVIATION = 0.0002;
    
    export function useMap() {
      const mapRef = useRef(null);
      const [selectedMarker, setSelectedMarker] = useState(null);
    
      const handleNavigateToPoint = useCallback(
        (id, lat, long) => {
          if (mapRef) {
            mapRef.current.animateCamera(
              {
                center: {
                  latitude: lat - DEVIATION,
                  longitude: long,
                },
                zoom: 18.5,
              },
              500
            );
          }
          setSelectedMarker(id);
        },
        [mapRef, setSelectedMarker]
      );
    
      const handelResetInitialPosition = useCallback(() => {
        if (mapRef) {
          mapRef.current.animateToRegion(
            {
              latitude: 41.3995345,
              longitude: 2.1909796,
              latitudeDelta: 0.003,
              longitudeDelta: 0.003,
            },
            500
          );
          setSelectedMarker(null);
        }
      }, [mapRef, setSelectedMarker]);
    
      return {
        mapRef,
        selectedMarker,
        handleNavigateToPoint,
        handelResetInitialPosition,
      };
    }
    
    上のコードでわかるように、関数はとても簡単です.The animateCamera 関数は、パラメータとして受け取る:緯度と経度、ズームとアニメーションがかかる時間と中心.の場合animateToRegion 関数は、論理が非常に似ていますが、型カメラを使用する代わりに、タイプ領域を使用します.
    我々のケースではsetSelectedMarker カメラがセンターとしてそれを使うとき、マーカーを拡大することができます.
    フックを使用するには、単に私たちのマップのコンポーネントに追加する必要があります.しかし、その前に、フック関数を使用できるようにマップの上にコンポーネントを作成します.
    我々は、これらのいずれかをクリックすると、その点に移動し、選択されたマーカーが展開される場所のリストをボトムシートのコンポーネントを作成するつもりです.コンポーネントのために我々は、コンポーネントアニメーションを管理するために再利用を使用して';反応ネイティブスクロールボトムシート';ライブラリを使用している.
    //BottomSheet.js
    import React from 'react';
    import { Dimensions, StyleSheet, View } from 'react-native';
    import ScrollBottomSheet from 'react-native-scroll-bottom-sheet';
    import { MARKERS_DATA } from '../../data';
    import { ListItem } from './ListItem';
    
    const windowHeight = Dimensions.get('window').height;
    
    export function BottomSheet({ onPressElement }) {
      return (
        <ScrollBottomSheet
          componentType="FlatList"
          snapPoints={[100, '50%', windowHeight - 200]}
          initialSnapIndex={1}
          renderHandle={() => (
            <View style={styles.header}>
              <View style={styles.panelHandle} />
            </View>
          )}
          data={MARKERS_DATA}
          keyExtractor={(i) => i.id}
          renderItem={({ item }) => (
            <ListItem item={item} onPressElement={onPressElement} />
          )}
          contentContainerStyle={styles.contentContainerStyle}
        />
      );
    }
    
    const styles = StyleSheet.create({
      contentContainerStyle: {
        flex: 1,
        backgroundColor: 'white',
      },
      header: {
        alignItems: 'center',
        backgroundColor: 'white',
        paddingVertical: 20,
      },
      panelHandle: {
        width: 41,
        height: 4,
        backgroundColor: '#E1E1E1',
        borderRadius: 17,
      },
    });
    
    また、私たちのマップの状態をリセットできるようになるトップメニューを追加します.
    //TopBar.js
    import React from 'react';
    import { StyleSheet, View } from 'react-native';
    import { Avatar } from './Avatar';
    import { RefreshButton } from './RefreshButton';
    
    export function TopBar({ onPressElement }) {
      return (
        <View style={styles.container}>
          <Avatar />
          <RefreshButton onPressElement={onPressElement} />
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        position: 'absolute',
        left: 0,
        top: 40,
        width: '100%',
        zIndex: 1,
        flexDirection: 'row',
        justifyContent: 'space-between',
        paddingHorizontal: 10,
      },
    });
    
    最後に、マップコンポーネントはこのようになります.
    import React from 'react';
    import { StyleSheet, View, Dimensions } from 'react-native';
    import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
    import { TopBar, BottomSheet, CustomMarker } from '../../components';
    import { MARKERS_DATA } from '../../data';
    import { useMap } from './useMap';
    import { mapStyle } from './mapStyle';
    
    export function MapScreen() {
      const {
        mapRef,
        selectedMarker,
        handleNavigateToPoint,
        handelResetInitialPosition,
      } = useMap();
    
      return (
        <View style={styles.container}>
          <TopBar onPressElement={handelResetInitialPosition} />
          <MapView
            ref={mapRef}
            customMapStyle={mapStyle}
            provider={PROVIDER_GOOGLE}
            style={styles.mapStyle}
            initialRegion={{
              latitude: 41.3995345,
              longitude: 2.1909796,
              latitudeDelta: 0.003,
              longitudeDelta: 0.003,
            }}
            mapType="standard"
          >
            {MARKERS_DATA.map((marker) => (
              <CustomMarker
                key={marker.id}
                id={marker.id}
                selectedMarker={selectedMarker}
                color={marker.color}
                latitude={marker.latitude}
                longitude={marker.longitude}
              ></CustomMarker>
            ))}
          </MapView>
          <BottomSheet onPressElement={handleNavigateToPoint} />
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: 'black',
        alignItems: 'center',
        justifyContent: 'center',
      },
      mapStyle: {
        width: Dimensions.get('window').width,
        height: Dimensions.get('window').height,
      },
    });
    
    我々は非常に直感的な方法で興味の異なる点の間のナビゲーションを管理することができます非常にシンプルなインターフェイスでマップアプリケーションを構築するために管理している.はるかに複雑な製品は、この上に構築することができますが、それは良いスタートポイントは、2020年に反応ネイティブで地図アプリを開発している場合.

    完全プロジェクトはavailable on GitHub それで、あなたはそれをダウンロードして、働くことができます.
    この記事は、当初は2007年に出版されましたReboot Blog .