박상권의 삽질블로그

[안드로이드/Android]Google Map에 Custom Marker 추가하는 방법 본문

IT/Android-TIP (한글)

[안드로이드/Android]Google Map에 Custom Marker 추가하는 방법

박상권 2016. 4. 26. 09:55

안드로이드 개발자들이 모여있는 오픈채팅방에 참여해보세요 .
Q&A 및 팁을 공유하는 방입니다..
오픈채팅방 참여



블로그를 Medium으로 옮겨서 운영하고 있습니다.
앞으로 새로운 글은 모두 미디엄 블로그를 통해서 올릴 예정입니다.
미디엄에서 다양하고 유익한 포스팅을 살펴보세요
미디엄 블로그 보기


우리는 구글지도를 사용하면서 지도위에 찍는 마커를 사용합니다.

기본 Pin과 Pin을 눌렀을때 나오는 Snippet이 있지만 기본 Pin형식의 마커가 아닌 특정 정보를 보여주는 마커를 보여주고 싶을때가 있습니다.



먼저 아래 예시 이미지를 보겠습니다.

에어비앤비와 셀폰 서비스에서 보듯이 서버로부터 상점 혹은 호스트의 정보들을 가져와서 해당 위치에 맞게 마커를 찍어줍니다.

그리고 마커가 그냥 Pin모양이 아니라 가격정보 혹은 상점 이름으로 표시해주는 기능이 구현되어 있습니다.


                






참고로 에어비앤비는 자체적으로 기술블로그를 운영하고있습니다.

여기서 많은 정보를 얻을수 있고 유용한 오픈소스도 살펴볼 수 있습니다.

사랑합니다 에어비앤비








보통은 아래와 같은 마커였겠지만 위의 에어비엔비나 셀폰의 화면이 좀더 사용자가 필요한 정보만을 직관적으로 보여줄수 있습니다.









마커를 Custom해서 우리가 보여주고자하는 정보 자체를 마커로 사용하는 방법에 대해 포스팅해보겠습니다.




먼저 최종결과물은 아래와 같이 움직이게 될것입니다.




위치,가격정보를 받아와서 가격정보를 마커자체로 표시해주고,

마커가 선택되었을때 선택된 마커만 선택되어있음을 표시해주고 해당 마커를 지도의 가운데로 위치시킵니다.


설명없이 바로 샘플프로젝트를 돌려보고 싶으신분들은 Github에서 받아보실 수 있습니다.






# 구현 순서


1. 구글지도 프로젝트 준비

2. 선택/비선택 배경이미지 준비(9patch 이미지)

3. 커스텀마커를 위한 xml 생성

4. 마커에 사용될 위치/기타정보를 포함하는 Class 생성

5. 샘플 마커정보 목록 생성

6. 마커정보로부터 하나씩 마커 추가

7. 마커가 선택되었을때 수행할 작업 설정

8. 기타 작업










1. 구글지도 프로젝트 준비하기


Google Maps Android API를 참조해서 구글지도를 설정할수도 있지만 New Project에서 구글지도 프로젝트를 바로 만들어 줄수도 있습니다.




물론 지도를 로딩하는데 필요한 API KEY는 각자 Google API콘솔에서 발급 받아야 합니다.




2. 선택/비선택 배경이미지 준비


마커의 배경으로 사용할 이미지는 9patch이미지로 준비되어야 합니다.

가격정보를 TextView에 설정해줄때 9patch이미지로 이미지가 늘어날 부분을 결정해주고 글자가 들어갈 크기도 지정해 줄 수 있습니다.

9patch 이미지에 대해 아직 감이 안오신다면 검색을 통해서 익혀보시기 바랍니다.

(추후 블로그주제로 다루겠습니다.)

미리 제가 만들어둔 아래의 이미지를 사용하셔도 되고 9patch이미지가 준비되어 있다면 그 이미지를 사용하셔도 좋습니다.


        







3. 커스텀마커를 위한 xml 생성


커스텀 마커 xml은 간단합니다.

FrameLayout아래에 가격정보를 설정할 TextView를 넣어주고 배경이미지로 위의 9patch이미지를 설정해줍니다.








4. 마커에 사용될 위치/기타정보를 포함하는 클래스 생성


예를들어 상점이라면 상점과 관련된 많은 정보들이 있을겁니다.

하지만 여기서는 상점이라고 예를들었을때 해당 상점의 위치와 판매하는 가격정보 3가지만을 가지고 있는 클래스를 만들겠습니다.

각자 서비스에 맞게 여러가지 변수정보들을 포함시켜주시면 됩니다.








5. 샘플 마커정보 목록 생성


위에서 만든 클래스의 샘플리스트를 만들어 줍니다.

실제로는 서버에서 해당 정보들의 목록을 받아오고 그 목록을 마커로 추가해주어야 합니다.




private void getSampleMarkerItems() {
ArrayList<MarkerItem> sampleList = new ArrayList();


sampleList.add(new MarkerItem(37.538523, 126.96568, 2500000));
sampleList.add(new MarkerItem(37.527523, 126.96568, 100000));
sampleList.add(new MarkerItem(37.549523, 126.96568, 15000));
sampleList.add(new MarkerItem(37.538523, 126.95768, 5000));


for (MarkerItem markerItem : sampleList) {
addMarker(markerItem, false);
}

}


샘플로 4개의 MarkerItem을 만들고 추가해줍니다.

완성된 샘플리스트에서 커스텀 마커를 추가하는 addMarker()를 각각 호출해줍니다.







6. 마커정보로부터 하나씩 마커 추가


먼저 위에서 커스텀마커를 위해 만들어둔 xml을 가져옵니다.


private void setCustomMarkerView() {

marker_root_view = LayoutInflater.from(this).inflate(R.layout.marker_layout, null);
tv_marker = (TextView) marker_root_view.findViewById(R.id.tv_marker);
}

setCustomMarkerView() 은 onCreate()나 onMapReady()처럼 액티비티가 초기화되는 부분에 미리 뷰를 설정해주어야 합니다.




다음 실제 마커를 추가하는 addMarker()함수를 만들어줍니다.



private Marker addMarker(MarkerItem markerItem, boolean isSelectedMarker) {


LatLng position = new LatLng(markerItem.getLat(), markerItem.getLon());
int price = markerItem.getPrice();
String formatted = NumberFormat.getCurrencyInstance().format((price));

tv_marker.setText(formatted);

if (isSelectedMarker) {
tv_marker.setBackgroundResource(R.drawable.ic_marker_phone_blue);
tv_marker.setTextColor(Color.WHITE);
} else {
tv_marker.setBackgroundResource(R.drawable.ic_marker_phone);
tv_marker.setTextColor(Color.BLACK);
}

MarkerOptions markerOptions = new MarkerOptions();
markerOptions.title(Integer.toString(price));
markerOptions.position(position);
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(this, marker_root_view)));


return mMap.addMarker(markerOptions);

}

마커를 추가하기위해서 우리는 마커와 관련된 정보인 MarkerItem과 해당마커가 선택되어있는지 아닌지에 대한 isSelectedMarker정보를 받아옵니다.

MarkerItem에서 위치정보와 가격정보를 받아옵니다.

받아온 가격정보는 1000같은 숫자이지만 이 값을 W1,000 과 같이 표시해주기 위해 NumberFormat.getCurrencyInstance().format()를 사용합니다.


만약 선택되어있는 마커로 표시되어야 한다면 배경이미지를 파란색, 글씨색을 흰색으로 해주고

그렇지 않다면 배경이미지를 흰색, 글씨색을 검은색으로 설정하도록 해줍니다.


MarkerOptions를 만들어주고 지도에 add하는 작업만 해주면 끝나는데 여기서 우리가 설정한 View를 Bitmap으로 변환해주는 작업이 필요합니다.

createDrawableFromView() 함수가 이 작업을 수행해 줍니다.



// View를 Bitmap으로 변환
private Bitmap createDrawableFromView(Context context, View view) {

DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
view.measure(displayMetrics.widthPixels, displayMetrics.heightPixels);
view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
view.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);
view.draw(canvas);

return bitmap;
}


이 함수는 넘겨받은 View를 Bitmap으로 변환하는 작업을 해주는데 buildDrawingCache()를 통해서 Bitmap으로 변환해줄수 있습니다.




이렇게까지 하시면 우리는 샘플리스트의 각각의 정보를 우리가 만든 커스텀 마커로 만들고 지도에 표시해주는 작업을 해줄수가 있습니다.




하지만 이게 끝은 아니죠

마커가 선택되었을때의 이미지를 추가해주어야 합니다.







7. 마커가 선택되었을때 수행할 작업 설정


mMap.setOnMarkerClickListener(this);

먼저 구글지도에 markerClickListener를 달아줍니다.







@Override
public boolean onMarkerClick(Marker marker) {

CameraUpdate center = CameraUpdateFactory.newLatLng(marker.getPosition());
mMap.animateCamera(center);

changeSelectedMarker(marker);

return true;
}

onMarkerClick()함수에서는 마커가 클릭되면 현재 클릭된 마커를 지도의 가운데로 이동시키고, 

클릭된 마커를 선택된 마커로 변환해주는 함수를 호출합니다.






private void changeSelectedMarker(Marker marker) {
// 선택했던 마커 되돌리기
if (selectedMarker != null) {
addMarker(selectedMarker, false);
selectedMarker.remove();
}

// 선택한 마커 표시
if (marker != null) {
selectedMarker = addMarker(marker, true);
marker.remove();
}


}

changeSelectedMarker()함수는 약간의 눈속임 작업이 들어갑니다.


사용자가 봤을때는 선택되었던게 선택되지않은 이미지로, 선택되지 않았던게 선택된 이미지로 바뀌는것처럼 보일것입니다.


하지만 실제는 아래와 같은 작업이 이루어집니다.

1. 기존에 선택되어있는 마커정보를 이용해서 같은 내용의 선택되지않은 마커를 추가

2. 기존에 선택되어있는 마커를 지도에서 삭제

3. 새로 선택된 마커를 선택되어있는 마커정보로 추가

4. 새로 선택된 마커를 지도에서 삭제



또한 같은 addMarker()지만 마커로부터 정보를 가져와서 위에 선언했던 원래의 addMarker()함수를 사용하기위해 별도의 addMarker()함수를 만들어줍니다.


private Marker addMarker(Marker marker, boolean isSelectedMarker) {
double lat = marker.getPosition().latitude;
double lon = marker.getPosition().longitude;
int price = Integer.parseInt(marker.getTitle());
MarkerItem temp = new MarkerItem(lat, lon, price);
return addMarker(temp, isSelectedMarker);

}







8. 기타 작업


구지 해주지 않아도 되는 작업이지만 필요하다면 추가해줄 작업도 있습니다.

우리는 사용자의 편리함을 추구하니까요

사랑합니다 고갱님...






마커가 아닌 지도가 선택되었을때 기존에 선택되어있는 마커는 선택되지 않은것으로 바꿔주는 작업을 해줍니다.

mMap.setOnMapClickListener(this);

먼저 지도에 클릭리스너를 달아주고 마커가 아닌 지도가 클릭되면 기존의 선택되어있는 마커를 선택되지않은 마커로 변환해주는 작업을 수행합니다.


@Override
public void onMapClick(LatLng latLng) {
changeSelectedMarker(null);
}





아래는 최종 MapsActivity의 소스코드 입니다.





물론 이 프로젝트는 Github에서도 확인해보실수 있습니다.

(내용이 유용하셨다면 Github 오른쪽위의 [Star]버튼을 눌러주시면 감사하겠습니다

저에게는 별풍선 주시는 효과가 있어요!)





다음번에는 구글지도의 Clustering기능을 이용해서 마커가 한번에 몰려있을때는 개수로 표시해주고 줌인을 했을대 상세 마커들을 표시해주는 방법에 대해서 포스팅해보겠습니다.

Clustering이 적용된 화면은 아래와 같습니다.







감사합니다.





Comments