이 글에서는 안드로이드 카카오 지도 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 |