지도에서 현재 장소 선택 및 세부정보 표시하기

이 가이드에서는 Android 기기의 현재 위치를 찾고 해당 위치에 있는 장소(비즈니스 또는 기타 관심 장소)의 세부정보를 표시하는 방법을 설명합니다. Maps SDK for Android, Places SDK for Android, Google Play 서비스 Location API의 통합 위치 정보 제공자를 사용하여 Android 앱을 빌드하려면 이 가이드를 따르세요.

코드 가져오기

GitHub에서 Google Maps Android API v2 샘플 저장소를 복제하거나 다운로드합니다.

개발 프로젝트 설정

다음 단계에 따라 Android 스튜디오에서 가이드 프로젝트를 만듭니다.

  1. Android 스튜디오를 다운로드하여 설치합니다.
  2. Android 스튜디오에 Google Play 서비스 패키지를 추가합니다.
  3. 이 가이드를 읽기 전에 미리 하지 않은 경우 Google Maps Android API v2 샘플 저장소를 복제하거나 다운로드합니다.
  4. 가이드 프로젝트를 가져옵니다.

    • Android 스튜디오에서 File > New > Import Project를 선택합니다.
    • Google Maps Android API v2 샘플 저장소를 다운로드한 후 저장한 위치로 이동합니다.
    • 다음 위치에서 CurrentPlaceDetailsOnMap 프로젝트를 찾습니다.
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • 프로젝트 디렉터리를 선택한 다음 OK를 클릭합니다. 이제 Android 스튜디오에서 Gradle 빌드 도구를 사용하여 프로젝트를 빌드합니다.

API 키 가져오기 및 필요한 API 사용 설정

이 가이드를 완료하려면 Maps SDK for Android 및 Places SDK for Android를 사용할 수 있도록 승인된 Google API 키가 필요합니다.

다음 버튼을 클릭하여 키를 가져오고 API를 활성화합니다.

시작하기

자세한 내용은 API 키 가져오기에 관한 전체 가이드를 참고하세요.

앱에 API 키 추가

  1. 프로젝트의 gradle.properties 파일을 수정합니다.
  2. API 키를 GOOGLE_MAPS_API_KEY 속성의 값에 붙여넣습니다.

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    앱을 빌드할 때 Gradle에서 API 키를 앱의 Android 매니페스트에 복사합니다. 앱의 build.gradle 파일에는 다음과 같이 매니페스트의 google_maps_key 문자열을 gradle 속성 GOOGLE_MAPS_API_KEY로 매핑하는 행이 포함되어 있습니다

    resValue "string", "google_maps_key",
            (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
    

앱 빌드 및 실행

  1. 컴퓨터에 Android 기기를 연결합니다. 안내에 따라 Android 기기에서 개발자 옵션을 사용 설정하고 기기를 감지하도록 시스템을 구성합니다. (또는 Android Virtual DeviceAndroid(AVD) Manager를 사용하여 가상 기기를 구성할 수 있습니다. 에뮬레이터를 선택할 때 Google API가 포함된 이미지를 선택해야 합니다. 자세한 내용은 시작 가이드를 참고하세요.)
  2. Android 스튜디오에서 Run 메뉴 옵션(또는 재생 버튼 아이콘)을 클릭합니다. 표시되는 메시지에 따라 기기를 선택합니다.

Android 스튜디오에서 Gradle을 호출하여 앱을 빌드한 다음 기기 또는 에뮬레이터에서 앱을 실행합니다. 이 페이지의 이미지와 비슷하게, 내 현재 위치를 중심으로 주변에 여러 개의 아이콘이 있는 지도가 표시됩니다.

문제해결:

  • 지도가 표시되지 않으면 위에서 설명한 대로 API 키를 가져와 앱에 추가했는지 확인합니다. Android 스튜디오의 Android Monitor 로그에 API 키에 대한 오류 메시지가 있는지 확인합니다.
  • 지도에서 시드니 하버 브리지(앱에서 지정된 기본 위치)에만 아이콘이 표시되면 앱에 위치 정보 액세스 권한을 부여했는지 확인하세요. Android 권한 가이드에 설명된 패턴에 따라 앱에서 런타임에 위치 정보 액세스 권한을 요청하는 메시지를 표시합니다. Settings > Apps > app name > Permissions > Location을 선택하여 기기에서 직접 권한을 설정할 수도 있습니다. 코드에서 권한을 처리하는 방법은 아래의 앱에서 위치 정보 액세스 권한 요청 가이드를 참고하세요.
  • Android 스튜디오 디버깅 도구를 사용하여 로그를 확인하고 앱을 디버깅합니다.

코드 이해하기

여기에서는 유사한 앱을 빌드하는 방법을 이해할 수 있도록 CurrentPlaceDetailsOnMap 앱의 가장 중요한 부분을 설명합니다.

Places API 클라이언트 인스턴스화

다음 인터페이스는 Places SDK for Android의 기본 진입점을 제공합니다.

  • GeoDataClient는 Google의 지역 장소 및 비즈니스 정보 데이터베이스에 대한 액세스 권한을 제공합니다.
  • PlaceDetectionClient는 기기의 현재 장소에 신속하게 액세스할 수 있는 권한을 제공하며 특정 장소에서 기기의 위치를 보고할 수 있는 기회를 제공합니다.

LocationServices 인터페이스는 Android 위치 서비스의 기본 진입점입니다.

API를 사용하려면 다음 코드 샘플에 표시된 대로 프래그먼트 또는 활동의 onCreate() 메서드에서 GeoDataClient, PlaceDetectionClient, FusedLocationProviderClient를 인스턴스화하세요.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Construct a GeoDataClient.
    mGeoDataClient = Places.getGeoDataClient(this, null);

    // Construct a PlaceDetectionClient.
    mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

    // Construct a FusedLocationProviderClient.
    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}

위치 정보 액세스 권한 요청

기기의 위치를 확인하고 사용자가 지도에서 내 위치 버튼을 탭할 수 있도록 허용하려면 앱에서 위치 정보 액세스 권한을 요청해야 합니다.

이 가이드에서는 상세한 위치 정보 액세스 권한을 요청하는 데 필요한 코드를 제공합니다. 자세한 내용은 Android 권한 가이드를 참고하세요.

  1. Android 매니페스트에 권한을 <manifest> 요소의 하위 요소로 추가합니다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.currentplacedetailsonmap">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. 앱에서 런타임 권한을 요청하여 사용자에게 위치 정보 액세스 권한을 허용 또는 거부할 수 있는 기회를 제공합니다. 다음 코드는 사용자가 상세한 위치 정보 액세스 권한을 부여했는지 확인합니다. 부여하지 않은 경우 권한을 요청합니다.

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
    
  3. 권한 요청의 결과를 처리하도록 onRequestPermissionsResult() 콜백을 재정의합니다.

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }
    

    이 가이드의 후반 섹션에서 updateLocationUI() 메서드에 대해 설명합니다.

지도 추가

Maps SDK for Android를 사용하여 지도를 표시합니다.

  1. 활동의 레이아웃 파일인 activity_maps.xml<fragment> 요소를 추가합니다. 이 요소는 지도의 컨테이너 역할을 하고 GoogleMap 객체에 대한 액세스 권한을 제공하도록 SupportMapFragment를 정의합니다. 이 가이드에서는 이전 버전 Android 프레임워크와의 호환성을 보장하기 위해 지도 프래그먼트의 Android 지원 라이브러리 버전을 사용합니다.

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.currentplacedetailsonmap.MapsActivityCurrentPlace" />
    
    
  2. 활동의 onCreate() 메서드에서 레이아웃 파일을 콘텐츠 뷰로 설정합니다.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. OnMapReadyCallback 인터페이스를 구현하고 GoogleMap 객체를 사용할 수 있을 때 지도를 설정하도록 onMapReady() 메서드를 재정의합니다.

    public void onMapReady(GoogleMap map) {
        mMap = map;
    
        // Do other setup activities here too, as described elsewhere in this tutorial.
    
        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();
    
        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }
    
  4. 활동의 onCreate() 메서드에서 FragmentManager.findFragmentById()를 호출하여 지도 프래그먼트의 핸들을 가져옵니다. 그런 다음 getMapAsync()를 사용하여 지도 콜백에 등록합니다.

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. updateLocationUI() 메서드를 작성하여 지도에 위치 컨트롤을 설정합니다. 사용자가 위치 정보 액세스 권한을 부여한 경우 지도의 내 위치 레이어 및 관련 컨트롤을 사용 설정하고 그렇지 않은 경우 레이어와 컨트롤을 사용 중지하고 현재 위치를 null로 설정합니다.

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

Android 기기의 위치 가져오기 및 지도에 위치 지정하기

통합 위치 정보 제공자를 사용하여 마지막으로 알려진 기기의 위치를 찾은 다음 해당 위치를 사용하여 지도에 위치를 지정합니다. 이 가이드에서 필요한 코드를 제공합니다. 기기의 위치를 가져오는 데 대한 자세한 내용은 Google Play 서비스 Location API의 통합 위치 정보 제공자 가이드를 참고하세요.

private void getDeviceLocation() {
    /*
     * Get the best and most recent location of the device, which may be null in rare
     * cases when a location is not available.
     */
    try {
        if (mLocationPermissionGranted) {
            Task locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener() {
                @Override
                public void onComplete(@NonNull Task task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                new LatLng(mLastKnownLocation.getLatitude(),
                                        mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.");
                        Log.e(TAG, "Exception: %s", task.getException());
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch(SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}

현재 장소 가져오기

Places SDK for Android를 사용하면 현재 기기 위치의 예상 장소 목록을 가져올 수 있습니다. 이 컨텍스트에서 장소는 비즈니스 또는 기타 관심 장소입니다.

이 가이드에서는 사용자가 장소 가져오기 버튼을 클릭할 때 장소를 가져옵니다. 장소를 선택할 수 있는 예상 장소 목록을 사용자에게 제공한 다음 지도에서 선택한 장소의 위치에 아이콘을 추가합니다. 이 가이드에서는 Places SDK for Android와 상호작용하는 데 필요한 코드를 제공합니다. 자세한 내용은 현재 장소 가져오기 가이드를 참고하세요.

  1. 옵션 메뉴에 대한 레이아웃 파일(current_place_menu.xml)을 만들고 옵션 메뉴를 설정하도록 onCreateOptionsMenu() 메서드를 재정의합니다. 코드와 함께 제공되는 샘플 앱을 참고하세요.
  2. 사용자가 장소 가져오기 옵션을 클릭할 때 현재 장소를 가져오도록 onOptionsItemSelected() 메서드를 재정의합니다.
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. showCurrentPlace() 메서드를 작성하여 현재 기기 위치의 예상 장소 목록을 가져옵니다.

    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }
    
        if (mLocationPermissionGranted) {
            // Use fields to define the data types to return.
            List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                    Place.Field.LAT_LNG);
    
            // Use the builder to create a FindCurrentPlaceRequest.
            FindCurrentPlaceRequest request =
                    FindCurrentPlaceRequest.newInstance(placeFields);
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<FindCurrentPlaceResponse> placeResult =
                    mPlacesClient.findCurrentPlace(request);
            placeResult.addOnCompleteListener (new OnCompleteListener<FindCurrentPlaceResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                    if (task.isSuccessful() && task.getResult() != null) {
                        FindCurrentPlaceResponse likelyPlaces = task.getResult();
    
                        // Set the count, handling cases where less than 5 entries are returned.
                        int count;
                        if (likelyPlaces.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                            count = likelyPlaces.getPlaceLikelihoods().size();
                        } else {
                            count = M_MAX_ENTRIES;
                        }
    
                        int i = 0;
                        mLikelyPlaceNames = new String[count];
                        mLikelyPlaceAddresses = new String[count];
                        mLikelyPlaceAttributions = new List[count];
                        mLikelyPlaceLatLngs = new LatLng[count];
    
                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            mLikelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            mLikelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            mLikelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();
    
                            i++;
                            if (i > (count - 1)) {
                                break;
                            }
                        }
    
                        // Show a dialog offering the user the list of likely places, and add a
                        // marker at the selected place.
                        MapsActivityCurrentPlace.this.openPlacesDialog();
                    }
                    else {
                        Log.e(TAG, "Exception: %s", task.getException());
                    }
                }
            });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");
    
            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));
    
            // Prompt the user for permission.
            getLocationPermission();
        }
    }
    
  4. openPlacesDialog() 메서드를 작성하여 사용자가 예상 장소 목록에서 장소를 선택할 수 있는 양식을 표시합니다. 선택한 장소의 지도에 아이콘을 추가합니다. 아이콘 콘텐츠에는 장소의 이름과 주소, API에게 제공하는 모든 저작자 표시가 포함됩니다.

    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }
    
                // Add a marker for the selected place, with an info window
                // showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));
    
                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };
    
        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }
    
  5. 정보 창 콘텐츠에 대한 맞춤 레이아웃을 만듭니다. 이렇게 하면 정보 창에 여러 줄의 콘텐츠를 표시할 수 있습니다. 먼저 정보 창 제목의 텍스트 뷰, 스니펫의 다른 텍스트 뷰(정보 창의 텍스트 콘텐츠)가 포함된 XML 레이아웃 파일, custom_info_contents.xml을 추가합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layoutDirection="locale"
        android:orientation="vertical">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="#ff000000"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ff7f7f7f" />
    </LinearLayout>
    
    
  6. InfoWindowAdapter 인터페이스를 구현하여 레이아웃을 확장하고 정보 창 콘텐츠를 로드합니다.

    @Override
    public void onMapReady(GoogleMap map) {
        // Do other setup activities here too, as described elsewhere in this tutorial.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    
        @Override
        // Return null here, so that getInfoContents() is called next.
        public View getInfoWindow(Marker arg0) {
            return null;
        }
    
        @Override
        public View getInfoContents(Marker marker) {
            // Inflate the layouts for the info window, title and snippet.
            View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents, null);
    
            TextView title = ((TextView) infoWindow.findViewById(R.id.title));
            title.setText(marker.getTitle());
    
            TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
            snippet.setText(marker.getSnippet());
    
            return infoWindow;
          }
        });
    }
    

지도의 상태 저장

지도의 카메라 위치와 기기 위치를 저장합니다. 사용자가 Android 기기를 회전시키거나 구성을 변경하면 Android 프레임워크에서 지도 활동을 삭제하고 재구성합니다. 원활한 사용자 환경을 유지하려면 관련성 높은 애플리케이션 상태를 저장했다가 필요할 때 복원하는 것이 좋습니다.

이 가이드에서는 지도의 상태를 저장하는 데 필요한 모든 코드를 제공합니다. 자세한 내용은 savedInstanceState 번들 가이드를 참고하세요.

  1. 지도 활동에서 활동 상태를 저장하기 위한 키 값을 설정합니다.

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. 활동이 일시중지될 때 상태를 저장하도록 onSaveInstanceState() 콜백을 구현합니다.

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }
    
  3. 활동의 onCreate() 메서드에서 기기의 위치와 지도의 카메라 위치(이전에 저장한 경우)를 가져옵니다.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        ...
    }