이 글에서는 안드로이드 카카오 지도 API에서 커스텀 마커와 커스텀 말풍선의 사용법, 그리고 POIItemEventListener를 이용해 마커와 말풍선 클릭 시 이벤트를 추가하는 방법을 적는다.
커스텀 마커
커스텀 마커는 굉장히 쉽다. 지도에 마커 등록 시 마커 이미지를 CustomImage로 설정하면 된다.
class MainActivity : AppCompatActivity() { private lateinit var binding : ActivityMainBinding // 뷰 바인딩 private lateinit var mapView : MapView // 카카오 지도 뷰 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) val view = binding.root setContentView(view) mapView = binding.mapView // 카카오 지도 뷰 // 서울시청에 마커 추가 val marker = MapPOIItem() marker.apply { itemName = "서울시청" // 마커 이름 mapPoint = MapPoint.mapPointWithGeoCoord(37.5666805, 126.9784147) // 좌표 markerType = MapPOIItem.MarkerType.CustomImage // 마커 모양 (커스텀) customImageResourceId = R.drawable.이미지 // 커스텀 마커 이미지 selectedMarkerType = MapPOIItem.MarkerType.CustomImage // 클릭 시 마커 모양 (커스텀) customSelectedImageResourceId = R.drawable.이미지 // 클릭 시 커스텀 마커 이미지 isCustomImageAutoscale = false // 커스텀 마커 이미지 크기 자동 조정 setCustomImageAnchor(0.5f, 1.0f) // 마커 이미지 기준점 } mapView.addPOIItem(marker) } }
★ 참고로 마커 이미지는 비트맵만 가능하다. 벡터 이미지는 지원하지 않는다.
isCustomImageAutoscale은 마커 이미지 크기를 기기 해상도에 따라 조정할 것인지를 정하는 것인데
true로 할 경우 이미지 크기를 해상도에 맞게 조절한다. 기기에 따라 마커가 너무 크거나 작게 나올 수 있다.
false로 할 경우 기기 해상도에 상관없이 동일한 크기로 표시한다. 고해상도 기기에서 마커가 흐리게 나올 수 있다.
다음은 isCustomImageAutoscale 설정에 따른 HD, FHD, QHD 해상도에서 마커의 모습이다. 마커의 크기는 64x64px 이다.


커스텀 말풍선
CalloutBalloonAdapter를 상속받는 클래스를 만들어 레이아웃을 커스텀 말풍선으로 이용할 수 있다.
다음은 커스텀 말풍선으로 사용할 레이아웃이다.
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/ball_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4px" app:cardCornerRadius="8px"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="200dp" android:layout_height="wrap_content"> <TextView android:id="@+id/ball_tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:text="장소명" android:textColor="?android:attr/textColorPrimary" android:textSize="12sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/ball_tv_address" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="주소" android:textSize="10sp" app:layout_constraintStart_toStartOf="@+id/ball_tv_name" app:layout_constraintTop_toBottomOf="@+id/ball_tv_name" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:text="클릭해서 추가 정보" android:textSize="10sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/ball_tv_address" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView>

참고로 레이아웃의 모습을 이미지의 형태로 변경해 말풍선을 표시하므로, 레이아웃에 버튼 등을 추가해도 상호작용이 불가능하다.
다음은 메인 액티비티 코드다.
class MainActivity : AppCompatActivity() { private lateinit var binding : ActivityMainBinding // 뷰 바인딩 private lateinit var mapView : MapView // 카카오 지도 뷰 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) val view = binding.root setContentView(view) mapView = binding.mapView // 카카오 지도 뷰 mapView.setCalloutBalloonAdapter(CustomBalloonAdapter(layoutInflater)) // 커스텀 말풍선 등록 // 서울시청 마커 추가 val marker = MapPOIItem() marker.apply { itemName = "서울시청" // 마커 이름 mapPoint = MapPoint.mapPointWithGeoCoord(37.5666805, 126.9784147) markerType = MapPOIItem.MarkerType.CustomImage customImageResourceId = R.drawable.marker_blue selectedMarkerType = MapPOIItem.MarkerType.CustomImage customSelectedImageResourceId = R.drawable.marker_red isCustomImageAutoscale = false setCustomImageAnchor(0.5f, 1.0f) } mapView.addPOIItem(marker) } // 커스텀 말풍선 클래스 class CustomBalloonAdapter(inflater: LayoutInflater): CalloutBalloonAdapter { val mCalloutBalloon: View = inflater.inflate(R.layout.balloon_layout, null) val name: TextView = mCalloutBalloon.findViewById(R.id.ball_tv_name) val address: TextView = mCalloutBalloon.findViewById(R.id.ball_tv_address) override fun getCalloutBalloon(poiItem: MapPOIItem?): View { // 마커 클릭 시 나오는 말풍선 name.text = poiItem?.itemName // 해당 마커의 정보 이용 가능 address.text = "getCalloutBalloon" return mCalloutBalloon } override fun getPressedCalloutBalloon(poiItem: MapPOIItem?): View { // 말풍선 클릭 시 address.text = "getPressedCalloutBalloon" return mCalloutBalloon } } }
getCalloutBalloon: 마커 클릭 시 표시할 뷰 (말풍선)
getPressedCalloutBalloon: 말풍선 클릭 시 표시할 뷰
추가 정보로 setCalloutBalloonAdapter가 마커를 추가하는 부분보다 앞에 있어야 커스텀 말풍선이 표시된다.
(처음에 만들 때 계속 기본 말풍선이 표시되서 당황했다.)
다음은 실행결과 화면이다.


마커 & 말풍선 클릭 이벤트
POIItemEventListener를 상속받는 클래스를 만들어서 마커나 말풍선을 클릭했을 때 이벤트가 실행되도록 만들 수 있다.
이 글에서는 말풍선 클릭 시 대화상자가 표시되게 만들었다.
class MainActivity : AppCompatActivity() { private lateinit var binding : ActivityMainBinding // 뷰 바인딩 private lateinit var mapView : MapView // 카카오 지도 뷰 private val eventListener = MarkerEventListener(this) // 마커 클릭 이벤트 리스너 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) val view = binding.root setContentView(view) mapView = binding.mapView // 카카오 지도 뷰 mapView.setCalloutBalloonAdapter(CustomBalloonAdapter(layoutInflater)) // 커스텀 말풍선 등록 mapView.setPOIItemEventListener(eventListener) // 마커 클릭 이벤트 리스너 등록 // 서울시청 마커 추가 val marker = MapPOIItem() marker.apply { itemName = "서울시청" mapPoint = MapPoint.mapPointWithGeoCoord(37.5666805, 126.9784147) markerType = MapPOIItem.MarkerType.CustomImage customImageResourceId = R.drawable.marker_blue selectedMarkerType = MapPOIItem.MarkerType.CustomImage customSelectedImageResourceId = R.drawable.marker_red isCustomImageAutoscale = false setCustomImageAnchor(0.5f, 1.0f) } mapView.addPOIItem(marker) } // 커스텀 말풍선 클래스 class CustomBalloonAdapter(inflater: LayoutInflater): CalloutBalloonAdapter { val mCalloutBalloon: View = inflater.inflate(R.layout.balloon_layout, null) val name: TextView = mCalloutBalloon.findViewById(R.id.ball_tv_name) val address: TextView = mCalloutBalloon.findViewById(R.id.ball_tv_address) override fun getCalloutBalloon(poiItem: MapPOIItem?): View { // 마커 클릭 시 나오는 말풍선 name.text = poiItem?.itemName address.text = "getCalloutBalloon" return mCalloutBalloon } override fun getPressedCalloutBalloon(poiItem: MapPOIItem?): View { // 말풍선 클릭 시 address.text = "getPressedCalloutBalloon" return mCalloutBalloon } } // 마커 클릭 이벤트 리스너 class MarkerEventListener(val context: Context): MapView.POIItemEventListener { override fun onPOIItemSelected(mapView: MapView?, poiItem: MapPOIItem?) { // 마커 클릭 시 } override fun onCalloutBalloonOfPOIItemTouched(mapView: MapView?, poiItem: MapPOIItem?) { // 말풍선 클릭 시 (Deprecated) // 이 함수도 작동하지만 그냥 아래 있는 함수에 작성하자 } override fun onCalloutBalloonOfPOIItemTouched(mapView: MapView?, poiItem: MapPOIItem?, buttonType: MapPOIItem.CalloutBalloonButtonType?) { // 말풍선 클릭 시 val builder = AlertDialog.Builder(context) val itemList = arrayOf("토스트", "마커 삭제", "취소") builder.setTitle("${poiItem?.itemName}") builder.setItems(itemList) { dialog, which -> when(which) { 0 -> Toast.makeText(context, "토스트", Toast.LENGTH_SHORT).show() // 토스트 1 -> mapView?.removePOIItem(poiItem) // 마커 삭제 2 -> dialog.dismiss() // 대화상자 닫기 } } builder.show() } override fun onDraggablePOIItemMoved(mapView: MapView?, poiItem: MapPOIItem?, mapPoint: MapPoint?) { // 마커의 속성 중 isDraggable = true 일 때 마커를 이동시켰을 경우 } } }
context는 대화상자 표시 때문에 추가했다. 없어도 상관없다.
이벤트 리스너 클래스의 객체는 반드시 MainActivity의 가장 바깥쪽에서 생성되어야 한다.
onCreate에서 생성되거나 setPOIItemEventListener에 클래스를 바로 넣을 경우 작동하지 않는다.
다음은 실행 결과다.

'프로그래밍 > 안드로이드' 카테고리의 다른 글
[Android/Kotlin] SlidingUpPanel 간단하게 슬라이딩 레이아웃 구성 (0) | 2021.01.31 |
---|---|
[Android/KakaoAPI] 장소 검색 결과를 리사이클러 뷰에 추가 (0) | 2021.01.14 |
[Android/KakaoAPI] 카카오 장소 검색 (Retrofit) (0) | 2021.01.13 |
[Android/KakaoAPI] 카카오 지도에서 현재 위치 추적 (3) | 2021.01.12 |
[Android/KakaoAPI] 안드로이드 앱에서 카카오 지도 사용하기 (0) | 2021.01.10 |