오버레이 공통
오버레이는 지리적 정보를 시각적으로 나타내는 요소로, 개발자가 지도 위에 자유롭게 배치할 수 있습니다. 네이버 지도 SDK는 마커, 정보 창, 셰이프 등 다양한 유형의 오버레이를 제공합니다. 자세한 내용은 각 오버레이에 대한 문서를 참고하십시오.
추가 및 삭제
객체 생성
위치 오버레이를 제외한 모든 오버레이는 일반적인 자바 객체처럼 생성할 수 있습니다.
다음은 마커 객체를 생성하는 예제입니다.
Marker marker = new Marker();
Java
Marker marker = new Marker();
Kotlin
val marker = Marker()
위치 오버레이 객체는 NaverMap
의 locationOverlay
속성을 사용해 가져올 수 있습니다.
다음은 위치 오버레이 객체를 가져오는 예제입니다.
LocationOverlay locationOverlay = naverMap.getLocationOverlay();
Java
LocationOverlay locationOverlay = naverMap.getLocationOverlay();
Kotlin
val locationOverlay = naverMap.locationOverlay
오버레이 추가
위치 오버레이를 제외한 모든 오버레이는 map
속성을 지정해 오버레이를 지도에 추가할 수 있습니다. 위치 오버레이를 지도에 추가하는 방법은 위치 오버레이 문서를 참조하십시오.
다음은 마커를 지도에 추가하는 예제입니다.
정보 창은 map
속성 외에도 open()
메서드를 사용해 지도 또는 마커를 대상으로 열 수 있습니다.
다음은 마커를 대상으로 정보 창을 여는 예제입니다.
map
은 오버레이의 속성이므로, 하나의 오버레이는 동시에 두 개 이상의 지도에 나타날 수 없습니다. 이미 지도에 나타나 있는 오버레이에 새로이 map
속성을 지정하면 오버레이는 기존의 지도에서 사라지고 새로운 지도에 나타납니다.
오버레이 제거
위치 오버레이를 제외한 모든 오버레이는 map
속성을 null
로 지정해 지도에서 제거할 수 있습니다. 위치 오버레이를 지도에서 삭제하는 방법은 위치 오버레이 문서를 참조하십시오.
다음은 마커를 지도에서 제거하는 예제입니다.
정보 창은 map
속성 외에도 close()
메서드를 사용해 닫을 수 있습니다.
다음은 정보 창을 닫는 예제입니다.
멀티스레딩
오버레이 객체는 아무 스레드에서나 생성할 수 있습니다. 그러나 오버레이의 속성은 스레드 안전성이 보장되지 않으므로 여러 스레드에서 동시에 접근해서는 안됩니다. 특히 지도에 추가된 오버레이의 속성은 메인 스레드에서만 접근해야 하며, 그렇지 않으면 CalledFromWrongThreadException
이 발생합니다. 단, 오버레이가 지도에 추가되지 않았다면 다른 스레드에서 오버레이의 속성에 접근해도 예외가 발생하지 않습니다.
따라서 대량의 오버레이를 다룰 경우 객체를 생성하고 초기 옵션을 지정하는 작업은 백그라운드 스레드에서 수행하고 지도에 추가하는 작업만을 메인 스레드에서 수행하면 메인 스레드를 효율적으로 사용할 수 있습니다. 다음은 1000개의 마커를 백그라운드 스레드에서 생성하고 속성을 지정한 후 메인 스레드에서 지도에 추가하는 예제입니다.
Executor executor = ...
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {
// 백그라운드 스레드
List<Marker> markers = new ArrayList<>();
for (int i = 0; i < 1000; ++i) {
Marker marker = new Marker();
marker.setPosition(...);
marker.setIcon(...);
marker.setCaptionText(...);
markers.add(marker);
}
handler.post(() -> {
// 메인 스레드
for (Marker marker : markers) {
marker.setMap(naverMap);
}
});
});
Java
Executor executor = ...
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {
// 백그라운드 스레드
List<Marker> markers = new ArrayList<>();
for (int i = 0; i < 1000; ++i) {
Marker marker = new Marker();
marker.setPosition(...);
marker.setIcon(...);
marker.setCaptionText(...);
markers.add(marker);
}
handler.post(() -> {
// 메인 스레드
for (Marker marker : markers) {
marker.setMap(naverMap);
}
});
});
Kotlin
val executor: Executor = ...
val handler = Handler(Looper.getMainLooper())
executor.execute {
// 백그라운드 스레드
val markers = mutableListOf<Marker>()
repeat(1000) {
markers += Marker().apply {
position = ...
icon = ...
captionText = ...
}
}
handler.post {
// 메인 스레드
markers.forEach { marker ->
marker.map = naverMap
}
}
}
비트맵 사용
마커, 정보 창, 위치 오버레이, 지상 오버레이, 경로선 오버레이 등 많은 오버레이는 비트맵 이미지를 사용합니다. 오버레이에서 비트맵 이미지를 사용하려면 OverlayImage
객체를 만들어야 합니다.
OverlayImage 객체 생성
OverlayImage
는 아이콘으로 사용될 수 있는 비트맵 이미지를 나타내는 불변 클래스입니다. OverlayImage
클래스에 정의된 팩토리 메서드를 이용해 드로어블 리소스, 에셋, 비트맵 등으로부터 인스턴스를 생성할 수 있습니다. 다음은 몇 가지 팩토리 메서드에 대한 설명입니다.
fromResource()
: 드로어블 리소스로부터 객체를 생성합니다.fromAsset()
: 에셋으로부터 객체를 생성합니다.fromBitmap()
:Bitmap
으로부터 객체를 생성합니다.fromView()
:View
로부터 객체를 생성합니다. 메서드를 호출하는 순간 뷰가 렌더링되고, 렌더링된 결과가 비트맵으로 저장됩니다.
다음은 드로어블 리소스로부터 OverlayImage
객체를 생성하는 예제입니다.
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
Java
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
Kotlin
val image = OverlayImage.fromResource(R.drawable.marker_icon)
비트맵을 필요로 하는 오버레이의 속성에 OverlayImage
객체를 지정하면 이미지가 반영됩니다. 자세한 내용은 각 오버레이에 대한 문서를 참고하십시오.
다음은 마커의 아이콘을 지정하는 예제입니다.
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
marker.setIcon(image);
Java
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
marker.setIcon(image);
Kotlin
val image = OverlayImage.fromResource(R.drawable.marker_icon)
marker.icon = image
메모리 관리
OverlayImage
객체는 비트맵을 다루므로 사용 시 메모리 관리에 유의해야 합니다. 여러 오버레이가 같은 이미지를 사용할 경우 OverlayImage
인스턴스를 하나만 만들어 사용해야 합니다.
다음은 marker1
과 marker2
는 같은 비트맵을 공유하지만, marker3
과 marker4
는 동일한 비트맵을 중복해서 사용하는 잘못된 예제입니다.
// Good: marker1, 2가 같은 비트맵을 공유
OverlayImage image = OverlayImage.fromBitmap(bitmap)
marker1.setIcon(image);
marker2.setIcon(image);
// Bad: marker3, 4가 비트맵을 중복해서 사용
marker3.setIcon(OverlayImage.fromBitmap(bitmap));
marker4.setIcon(OverlayImage.fromBitmap(bitmap));
Java
// Good: marker1, 2가 같은 비트맵을 공유
OverlayImage image = OverlayImage.fromBitmap(bitmap)
marker1.setIcon(image);
marker2.setIcon(image);
// Bad: marker3, 4가 비트맵을 중복해서 사용
marker3.setIcon(OverlayImage.fromBitmap(bitmap));
marker4.setIcon(OverlayImage.fromBitmap(bitmap));
Kotlin
// Good: marker1, 2가 같은 비트맵을 공유
OverlayImage image = OverlayImage.fromBitmap(bitmap)
marker1.icon = image
marker2.icon = image
// Bad: marker3, 4가 비트맵을 중복해서 사용
marker3.icon = OverlayImage.fromBitmap(bitmap)
marker4.icon = OverlayImage.fromBitmap(bitmap)
단, 리소스와 에셋으로부터 OverlayImage
객체를 만들었다면, OverlayImage
가 나타내는 리소스와 에셋이 동일할 경우 인스턴스가 다르더라도 동일한 비트맵을 공유하게 됩니다.
다음은 marker1
, marker2
, marker3
, marker4
모두 동일한 비트맵을 공유하는 예제입니다.
// Good: marker1, 2가 같은 비트맵을 공유
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
marker1.setIcon(image);
marker2.setIcon(image);
// OK: marker3, 4가 다른 OverlayImage 객체를 사용하지만 참조하는 리소스가 같으므로 비트맵도 공유
marker3.setIcon(OverlayImage.fromResource(R.drawable.marker_icon));
marker4.setIcon(OverlayImage.fromResource(R.drawable.marker_icon));
Java
// Good: marker1, 2가 같은 비트맵을 공유
OverlayImage image = OverlayImage.fromResource(R.drawable.marker_icon);
marker1.setIcon(image);
marker2.setIcon(image);
// OK: marker3, 4가 다른 OverlayImage 객체를 사용하지만 참조하는 리소스가 같으므로 비트맵도 공유
marker3.setIcon(OverlayImage.fromResource(R.drawable.marker_icon));
marker4.setIcon(OverlayImage.fromResource(R.drawable.marker_icon));
Kotlin
// Good: marker1, 2가 같은 비트맵을 공유
val image = OverlayImage.fromResource(R.drawable.marker_icon)
marker1.icon = image
marker2.icon = image
// OK: marker3, 4가 다른 OverlayImage 객체를 사용하지만 참조하는 리소스가 같으므로 비트맵도 공유
marker3.icon = OverlayImage.fromResource(R.drawable.marker_icon)
marker4.icon = OverlayImage.fromResource(R.drawable.marker_icon)
지도가 감당할 수 없을 정도로 많은 비트맵이 사용되면 Logcat에 "Overlay image atlas overflow"라는 경고가 로깅되고 렌더링이 무척 느려집니다. 메모리 사용량이 더욱 증가해 시스템이 감당할 수 없을 정도가 되면 OOM(Out of memory)으로 프로세스가 종료될 수 있습니다.
겹침 우선순위
여러 오버레이가 화면에서 겹쳐지면 우선순위가 높은 오버레이가 낮은 오버레이를 덮습니다. 겹침 우선순위는 기본적으로 오버레이의 유형에 따라 지정되지만 변경할 수 있습니다.
전역 Z 인덱스
globalZIndex
속성을 사용하면 겹침 우선순위를 지정할 수 있습니다. globalZIndex
가 0
이상이면 심벌 위에, 0
미만이면 심벌 아래에 그려집니다. 단, globalZIndex
가 아무리 작더라도 지도 배경보다는 위에 그려집니다. 각 오버레이 유형별 globalZIndex
기본값은 다음과 같습니다.
- 정보 창:
400000
- 위치 오버레이:
300000
- 마커:
200000
- 화살표 오버레이:
100000
- (지도 심벌)
- 경로선 오버레이:
-100000
- 셰이프(폴리곤, 폴리라인, 서클):
-200000
- 지상 오버레이:
-300000
- (지도 배경)
예를 들어 경로선은 기본적으로 셰이프의 위, 지도 심벌의 아래에 그려집니다. 그러나 경로선의 globalZIndex
를 250000
으로 지정하면 마커의 위, 위치 오버레이의 아래에 그려집니다.
다음은 세 오버레이의 겹침 우선순위를 폴리곤 -> 경로선 -> 마커 순으로 지정하는 예제입니다.
polygon.setGlobalZIndex(150000);
path.setGlobalZIndex(50000);
marker.setGlobalZIndex(-50000);
Java
polygon.setGlobalZIndex(150000);
path.setGlobalZIndex(50000);
marker.setGlobalZIndex(-50000);
Kotlin
polygon.globalZIndex = 150000
path.globalZIndex = 50000
marker.globalZIndex = -50000
보조 Z 인덱스
zIndex
속성을 사용하면 globalZIndex
가 동일한 오버레이간의 겹침 우선순위를 지정할 수 있습니다. 즉, 두 오버레이의 globalZIndex
가 동일하다면 zIndex
가 큰 오버레이가 작은 오버레이를 덮습니다. 예를 들어 마커의 zIndex
를 1
로 지정하고, 지상 오버레이의 zIndex
를 2
로 지정했다 하더라도 마커의 기본 globalZIndex
는 200000
, 지상 오버레이의 기본 globalZIndex
는 -300000
이므로 마커가 지상 오버레이를 덮습니다. zIndex
의 기본값은 오버레이의 유형과 무관하게 0
이므로, 동일한 유형의 오버레이간의 우선순위만을 지정하고자 하는 경우 globalZIndex
보다 직관적으로 사용할 수 있습니다.
다음은 세 마커의 노출 순서를 노란색 -> 녹색 -> 파란색 순으로 지정하는 예제입니다.
yellowMarker.setZIndex(100);
greenMarker.setZIndex(0);
blueMarker.setZIndex(-10);
Java
yellowMarker.setZIndex(100);
greenMarker.setZIndex(0);
blueMarker.setZIndex(-10);
Kotlin
yellowMarker.zIndex = 100
greenMarker.zIndex = 0
blueMarker.zIndex = -10
노출 제어
모든 오버레이에는 가시성, 최소 및 최대 줌 레벨에 대한 속성이 있습니다. 오버레이를 일시적으로 또는 특정한 상황에만 숨기고자 할 경우 map
속성을 변경하는 것보다 이러한 속성을 사용하면 효율적입니다.
가시성
isVisible
속성을 false
로 지정하면 오버레이가 화면에서 숨겨집니다. 그러나 오버레이와 지도 간의 관계는 여전히 유지됩니다.
다음은 오버레이를 숨기는 예제입니다.
overlay.setVisible(false);
Java
overlay.setVisible(false);
Kotlin
overlay.isVisible = false
최소 및 최대 줌 레벨
minZoom
및 maxZoom
속성을 이용하면 특정 줌 레벨에서만 오버레이가 나타나도록 지정할 수도 있습니다. 카메라의 줌 레벨이 minZoom
과 maxZoom
범위를 벗어나면 오버레이가 숨겨집니다. minZoomInclusive
및 maxZoomInclusive
속성을 이용하면 최소/최대 줌 레벨에서 오버레이가 나타날지 여부를 지정할 수도 있습니다. 예를 들어 minZoom
이 15
이고 지도의 줌 레벨도 15
일 때, minZoomInclusive
가 true
라면 오버레이가 나타나고 false
라면 나타나지 않습니다.
다음은 오버레이가 12레벨 이상, 16레벨 미만에서만 나타나도록 지정하는 예제입니다.
overlay.setMinZoom(12);
overlay.setMinZoomInclusive(true);
overlay.setMaxZoom(16);
overlay.setMaxZoomInclusive(false);
Java
overlay.setMinZoom(12);
overlay.setMinZoomInclusive(true);
overlay.setMaxZoom(16);
overlay.setMaxZoomInclusive(false);
Kotlin
overlay.minZoom = 12.0
overlay.minZoomInclusive = true
overlay.maxZoom = 16.0
overlay.maxZoomInclusive = false
이벤트
모든 오버레이는 클릭 이벤트를 받고 소비할 수 있습니다. 오버레이에 대한 클릭 이벤트 리스너는 지도가 아닌 각 오버레이에 지정해야 합니다.
클릭 이벤트
setOnClickListener()
메서드로 Overlay.OnClickListener
를 지정하면 오버레이에 대한 클릭 이벤트를 받을 수 있습니다. 오버레이가 클릭되면 onClick()
콜백 메서드가 호출되며, 클릭된 오버레이 객체가 파라미터로 전달됩니다.
다음은 오버레이가 클릭될 때마다 "오버레이 클릭됨" 토스트가 노출되도록 클릭 리스너를 지정하는 예제입니다.
overlay.setOnClickListener(o -> {
Toast.makeText(context, "오버레이 클릭됨", Toast.LENGTH_SHORT).show();
return true;
});
Java
overlay.setOnClickListener(o -> {
Toast.makeText(context, "오버레이 클릭됨", Toast.LENGTH_SHORT).show();
return true;
});
Kotlin
overlay.setOnClickListener { o ->
Toast.makeText(context, "오버레이 클릭됨", Toast.LENGTH_SHORT).show()
true
}
하나의 오버레이에는 하나의 클릭 이벤트 리스너만 지정할 수 있습니다. 클릭 이벤트 리스너를 해제하려면 onClickListener
속성에 null
을 지정합니다.
다음은 오버레이의 클릭 리스너를 해제하는 예제입니다.
overlay.setOnClickListener(null);
Java
overlay.setOnClickListener(null);
Kotlin
overlay.onClickListener = null
이벤트 리스너는 이벤트를 받고자 하는 모든 오버레이에 추가해야 합니다. 만약 오버레이에 이벤트 리스너가 등록되어 있지 않다면 그 오버레이는 클릭 시 무시됩니다. 즉, 두 오버레이가 겹쳐져 있고 위에 있는 오버레이에 이벤트 리스너가 등록되어 있지 않다면 아래에 있는 오버레이가 이벤트를 받습니다. 아래에 오버레이가 없거나 이벤트 리스너가 등록되어 있지 않다면 지도가 클릭된 것으로 간주됩니다.
이벤트 전파 및 소비
오버레이의 클릭 이벤트는 지도로 전파될 수 있습니다. 이벤트를 지도로 전파하려면 Overlay.OnClickListener
의 onClick()
이 false
를 반환하도록 구현해야 합니다. 그렇게 하면 onClick()
이 반환된 후 지도의 OnClickListener.onClick()
이 호출됩니다. 반대로 onClick()
이 true
를 반환할 경우 오버레이가 이벤트를 소비한 것으로 간주되어 지도의 OnClickListener
는 호출되지 않습니다.
다음은 marker1
은 클릭 이벤트를 전파하고 marker2
는 소비하도록 하는 예제입니다. marker1
이 클릭될 때에는 "마커 클릭됨" 토스트와 "지도 클릭됨" 토스트가 모두 노출되지만, marker2
가 클릭될 때에는 "마커 2 클릭됨" 토스트만 노출됩니다.
marker1.setOnClickListener(overlay -> {
Toast.makeText(context, "마커 1 클릭됨", Toast.LENGTH_SHORT).show();
// 이벤트 전파
return false;
});
marker2.setOnClickListener(overlay -> {
Toast.makeText(context, "마커 2 클릭됨", Toast.LENGTH_SHORT).show();
// 이벤트 소비
return true;
});
naverMap.setOnMapClickListener((point, coord) -> {
Toast.makeText(context, "지도 클릭됨", Toast.LENGTH_SHORT).show();
});
Java
marker1.setOnClickListener(overlay -> {
Toast.makeText(context, "마커 1 클릭됨", Toast.LENGTH_SHORT).show();
// 이벤트 전파
return false;
});
marker2.setOnClickListener(overlay -> {
Toast.makeText(context, "마커 2 클릭됨", Toast.LENGTH_SHORT).show();
// 이벤트 소비
return true;
});
naverMap.setOnMapClickListener((point, coord) -> {
Toast.makeText(context, "지도 클릭됨", Toast.LENGTH_SHORT).show();
});
Kotlin
marker1.setOnClickListener {
Toast.makeText(context, "마커 1 클릭됨", Toast.LENGTH_SHORT).show()
// 이벤트 전파
false
}
marker2.setOnClickListener {
Toast.makeText(context, "마커 2 클릭됨", Toast.LENGTH_SHORT).show()
// 이벤트 소비
true
}
naverMap.setOnMapClickListener {
Toast.makeText(context, "지도 클릭됨", Toast.LENGTH_SHORT).show()
}
오버레이에서 이벤트가 전파되는 곳은 오직 지도뿐입니다. 즉, 이벤트가 발생한 오버레이의 아래에 다른 오버레이나 심벌이 겹쳐져 있더라도 이 오버레이나 심벌로는 이벤트가 전파되지 않습니다.
태그
tag
속성을 사용하면 오버레이에 추가적인 정보를 지정할 수 있습니다. 이 속성은 클릭 이벤트 리스너와 결합해 사용하면 특히 유용합니다. onClick()
메서드에서 태그를 확인해 어떤 오버레이가 클릭되었는지를 식별할 수 있으므로 여러 오버레이가 이벤트 리스너를 공유하도록 구현할 수 있습니다.
다음은 marker1
과 marker2
에 태그를 추가하고 클릭 이벤트 리스너를 공유하는 예제입니다.
Overlay.OnClickListener listener = overlay -> {
Toast.makeText(context, "마커 " + overlay.getTag() + " 클릭됨",
Toast.LENGTH_SHORT).show();
return false;
};
marker1.setTag(1);
marker2.setTag(2);
marker1.setOnClickListener(listener);
marker2.setOnClickListener(listener);
Java
Overlay.OnClickListener listener = overlay -> {
Toast.makeText(context, "마커 " + overlay.getTag() + " 클릭됨",
Toast.LENGTH_SHORT).show();
return false;
};
marker1.setTag(1);
marker2.setTag(2);
marker1.setOnClickListener(listener);
marker2.setOnClickListener(listener);
Kotlin
val listener = Overlay.OnClickListener { overlay ->
Toast.makeText(context, "마커 ${overlay.tag} 클릭됨", Toast.LENGTH_SHORT)
.show()
false
}
marker1.tag = 1
marker2.tag = 2
marker1.onClickListener = listener
marker2.onClickListener = listener